UNPKG

houser-js-utils

Version:

A comprehensive collection of TypeScript utility functions for common development tasks including array manipulation, string processing, date handling, random number generation, validation, and much more.

1,565 lines 308 kB
var P = Object.defineProperty; var O = (e, t, r) => t in e ? P(e, t, { enumerable: !0, configurable: !0, writable: !0, value: r }) : e[t] = r; var D = (e, t, r) => O(e, typeof t != "symbol" ? t + "" : t, r); const Z = { /** * Gets the ARIA described by attribute of an element * @param element - The DOM element to check * @returns The value of aria-describedby attribute or null if not set * @example * ```typescript * const element = document.querySelector('.input'); * const describedBy = AccessibilityUtils.getAriaDescribedBy(element); * ``` */ getAriaDescribedBy(e) { return e.getAttribute("aria-describedby"); }, /** * Sets the ARIA described by attribute of an element * @param element - The DOM element to modify * @param describedBy - The ID(s) of the element(s) that describe this element * @example * ```typescript * const input = document.querySelector('.input'); * const helpText = document.querySelector('.help-text'); * AccessibilityUtils.setAriaDescribedBy(input, helpText.id); * ``` */ setAriaDescribedBy(e, t) { e.setAttribute("aria-describedby", t); }, /** * Gets the ARIA expanded state of an element * @param element - Element to check * @returns ARIA expanded state */ getAriaExpanded(e) { const t = e.getAttribute("aria-expanded"); return t === null ? null : t === "true"; }, /** * Sets the ARIA expanded state of an element * @param element - Element to modify * @param expanded - ARIA expanded state to set */ setAriaExpanded(e, t) { e.setAttribute("aria-expanded", t.toString()); }, /** * Gets the ARIA hidden state of an element * @param element - Element to check * @returns ARIA hidden state */ getAriaHidden(e) { const t = e.getAttribute("aria-hidden"); return t === null ? null : t === "true"; }, /** * Sets the ARIA hidden state of an element * @param element - Element to modify * @param hidden - ARIA hidden state to set */ setAriaHidden(e, t) { e.setAttribute("aria-hidden", t.toString()); }, /** * Gets the ARIA invalid state of an element * @param element - Element to check * @returns ARIA invalid state */ getAriaInvalid(e) { const t = e.getAttribute("aria-invalid"); return t === null ? null : t === "true"; }, /** * Sets the ARIA invalid state of an element * @param element - Element to modify * @param invalid - ARIA invalid state to set */ setAriaInvalid(e, t) { e.setAttribute("aria-invalid", t.toString()); }, /** * Gets the ARIA label of an element * @param element - Element to check * @returns ARIA label */ getAriaLabel(e) { return e.getAttribute("aria-label"); }, /** * Sets the ARIA label of an element * @param element - Element to modify * @param label - ARIA label to set */ setAriaLabel(e, t) { e.setAttribute("aria-label", t); }, /** * Gets the ARIA required state of an element * @param element - Element to check * @returns ARIA required state */ getAriaRequired(e) { const t = e.getAttribute("aria-required"); return t === null ? null : t === "true"; }, /** * Sets the ARIA required state of an element * @param element - Element to modify * @param required - ARIA required state to set */ setAriaRequired(e, t) { e.setAttribute("aria-required", t.toString()); }, /** * Gets the ARIA role of an element * @param element - Element to check * @returns ARIA role */ getAriaRole(e) { return e.getAttribute("role"); }, /** * Sets the ARIA role of an element * @param element - Element to modify * @param role - ARIA role to set */ setAriaRole(e, t) { e.setAttribute("role", t); }, /** * Removes focus from an element * @param element - Element to blur */ blurElement(e) { e instanceof HTMLElement && e.blur(); }, /** * Sets focus to an element * @param element - Element to focus */ focusElement(e) { e instanceof HTMLElement && e.focus(); }, /** * Sets focus to the first focusable element in a container * @param container - Container element */ focusFirstElement(e) { const t = this.getFocusableElements(e); t.length > 0 && t[0].focus(); }, /** * Sets focus to the last focusable element in a container * @param container - Container element */ focusLastElement(e) { const t = this.getFocusableElements(e); t.length > 0 && t[t.length - 1].focus(); }, /** * Sets focus to the next focusable element * @param currentElement - Current element */ focusNextElement(e) { const t = this.getFocusableElements(document.body), r = t.indexOf(e); r < t.length - 1 && t[r + 1].focus(); }, /** * Sets focus to the previous focusable element * @param currentElement - Current element */ focusPreviousElement(e) { const t = this.getFocusableElements(document.body), r = t.indexOf(e); r > 0 && t[r - 1].focus(); }, /** * Gets all focusable elements within a container * @param container - Container element * @returns Array of focusable elements */ getFocusableElements(e) { const t = e.querySelectorAll("*"); return Array.from(t).filter((r) => this.isFocusable(r)); }, /** * Gets the current focus element * @returns Currently focused element */ getFocusedElement() { return document.activeElement; }, /** * Checks if an element is focusable * @param element - Element to check * @returns True if element is focusable */ isFocusable(e) { if (!(e instanceof HTMLElement) || e.tabIndex < 0 || "disabled" in e && e.disabled) return !1; switch (e.tagName.toLowerCase()) { case "a": case "button": case "input": case "select": case "textarea": return !0; default: return !1; } }, /** * Traps focus within a container element, typically used for modals or dialogs * @param container - The container element to trap focus within * @returns A function that removes the focus trap when called * @example * ```typescript * const modal = document.querySelector('.modal'); * const removeTrap = AccessibilityUtils.trapFocus(modal); * * // Later, when the modal is closed: * removeTrap(); * ``` */ trapFocus(e) { const t = this.getFocusableElements(e), r = t[0], n = t[t.length - 1], a = (o) => { !(o instanceof KeyboardEvent) || o.key !== "Tab" || (o.shiftKey ? document.activeElement === r && (o.preventDefault(), n.focus()) : document.activeElement === n && (o.preventDefault(), r.focus())); }; return e.addEventListener("keydown", a), () => e.removeEventListener("keydown", a); } }, H = { /** * Collection of value parsers for AG Grid */ parsers: { /** * Parses a string value to a number * @param params - Object containing the new value to parse * @returns Parsed number or null if invalid * @example * ```typescript * const value = AGGridUtils.parsers.number({ newValue: '123.45' }); * // Returns: 123.45 * ``` */ number: (e) => { if (typeof e.newValue == "string" && e.newValue.trim() === "") return null; const t = Number(e.newValue); return isNaN(t) ? null : t; }, /** * Trims a string value * @param params - Object containing the new value to trim * @returns Trimmed string or null if empty * @example * ```typescript * const value = AGGridUtils.parsers.string({ newValue: ' hello ' }); * // Returns: 'hello' * ``` */ string: (e) => String(e.newValue).trim() }, /** * Collection of value setters for AG Grid */ setters: { /** * Creates a setter that rounds numbers up to the nearest integer * @param field - The field name to set the value on * @returns A value setter function * @example * ```typescript * const setter = AGGridUtils.setters.numberCeil('price'); * setter({ data: {}, newValue: 123.45 }); * // Sets data.price to 124 * ``` */ numberCeil: (e) => (t) => (t.data[e] = Math.ceil(t.newValue), !0), /** * Creates a setter that rounds numbers down to the nearest integer * @param field - The field name to set the value on * @returns A value setter function * @example * ```typescript * const setter = AGGridUtils.setters.numberFloor('price'); * setter({ data: {}, newValue: 123.45 }); * // Sets data.price to 123 * ``` */ numberFloor: (e) => (t) => (t.data[e] = Math.floor(t.newValue), !0), /** * Creates a setter that formats numbers to one decimal place if they're integers * @param field - The field name to set the value on * @returns A value setter function * @example * ```typescript * const setter = AGGridUtils.setters.numberFloat('price'); * setter({ data: {}, newValue: 123 }); * // Sets data.price to '123.0' * ``` */ numberFloat: (e) => (t) => { const r = Number(t.newValue); return t.data[e] = Number.isInteger(r) ? r.toFixed(1) : r, !0; } } }, _ = { /** * Animates an element using CSS transitions * @param element - The HTML element to animate * @param properties - CSS properties to animate * @param duration - Animation duration in milliseconds (default: 300) * @param easing - CSS easing function (default: 'ease') * @returns Promise that resolves when animation completes * @example * ```typescript * const element = document.querySelector('.box'); * await AnimationUtils.animate(element, { * transform: 'translateX(100px)', * opacity: '0.5' * }, 500, 'ease-in-out'); * ``` */ animate(e, t, r = 300, n = "ease") { return new Promise((a) => { Object.keys(t).forEach((s) => { t[s] !== void 0 && parseFloat( getComputedStyle(e)[s] ); }), e.style.transition = `all ${r}ms ${n}`, Object.keys(t).forEach((s) => { const i = t[s]; i !== void 0 && (e.style[s] = i); }); const o = () => { e.removeEventListener("transitionend", o), e.style.transition = "", a(); }; e.addEventListener("transitionend", o), setTimeout(o, r); }); }, /** * Animates an element using requestAnimationFrame for smoother animations * @param element - The HTML element to animate * @param properties - CSS properties to animate * @param duration - Animation duration in milliseconds (default: 300) * @param easing - Custom easing function (default: linear) * @returns Promise that resolves when animation completes * @example * ```typescript * const element = document.querySelector('.box'); * await AnimationUtils.animateWithRAF(element, { * transform: 'translateX(100px)' * }, 500, (t) => t * t); // Quadratic easing * ``` */ animateWithRAF(e, t, r = 300, n = (a) => a) { return new Promise((a) => { const o = performance.now(), s = {}, i = {}; Object.keys(t).forEach((u) => { const m = t[u]; if (m !== void 0) { const h = parseFloat( getComputedStyle(e)[u] ), d = parseFloat(m); !isNaN(h) && !isNaN(d) && (s[u] = h, i[u] = d); } }); const c = (u) => { const m = u - o, h = Math.min(m / r, 1), d = n(h); Object.keys(s).forEach((f) => { const w = s[f], g = i[f], M = w + (g - w) * d; e.style[f] = `${M}px`; }), h < 1 ? requestAnimationFrame(c) : a(); }; requestAnimationFrame(c); }); }, /** * Creates a keyframe animation using the Web Animations API * @param element - The HTML element to animate * @param keyframes - Array of keyframe objects * @param options - Animation options * @returns Animation object * @example * ```typescript * const element = document.querySelector('.box'); * const animation = AnimationUtils.createKeyframeAnimation(element, [ * { transform: 'translateX(0)', opacity: 1 }, * { transform: 'translateX(100px)', opacity: 0.5 } * ], { * duration: 500, * easing: 'ease-in-out' * }); * ``` */ createKeyframeAnimation(e, t, r = {}) { return e.animate(t, { duration: 300, easing: "ease", fill: "forwards", ...r }); }, /** * Creates a spring physics-based animation * @param element - The HTML element to animate * @param properties - CSS properties to animate * @param options - Spring animation configuration * @returns Promise that resolves when animation completes * @example * ```typescript * const element = document.querySelector('.box'); * await AnimationUtils.createSpringAnimation(element, { * transform: 'translateY(200px)' * }, { * stiffness: 170, * damping: 26, * mass: 1 * }); * ``` */ createSpringAnimation(e, t, r = {}) { const { stiffness: n = 170, damping: a = 26, mass: o = 1, duration: s = 300 } = r; return new Promise((i) => { const c = performance.now(), u = {}, m = {}, h = {}; Object.keys(t).forEach((f) => { const w = t[f]; if (w !== void 0) { const g = parseFloat( getComputedStyle(e)[f] ), M = parseFloat(w); !isNaN(g) && !isNaN(M) && (u[f] = g, m[f] = M, h[f] = 0); } }); const d = (f) => { const w = f - c; if (w >= s) { Object.keys(m).forEach((g) => { e.style[g] = `${m[g]}px`; }), i(); return; } Object.keys(u).forEach((g) => { const M = u[g], E = m[g], A = parseFloat( e.style[g] || M ), S = h[g], R = E - A, U = n * R, k = a * S, x = (U - k) / o; h[g] = S + x * (w / 1e3); const L = A + h[g] * (w / 1e3); e.style[g] = `${L}px`; }), requestAnimationFrame(d); }; requestAnimationFrame(d); }); }, /** * Gets all animations currently running on an element * @param element - The HTML element to check * @returns Array of Animation objects * @example * ```typescript * const element = document.querySelector('.box'); * const animations = AnimationUtils.getAnimations(element); * ``` */ getAnimations(e) { return e.getAnimations(); }, /** * Gets the current animation state of an element * @param element - The HTML element to check * @returns Current animation state: 'idle', 'running', or 'paused' * @example * ```typescript * const element = document.querySelector('.box'); * const state = AnimationUtils.getAnimationState(element); * if (state === 'running') { * console.log('Element is currently animating'); * } * ``` */ getAnimationState(e) { const t = e.getAnimations(); return t.length === 0 ? "idle" : t.some((r) => r.playState === "running") ? "running" : "paused"; }, /** * Checks if an element has any active animations * @param element - The HTML element to check * @returns True if the element has any animations * @example * ```typescript * const element = document.querySelector('.box'); * if (AnimationUtils.hasAnimations(element)) { * console.log('Element has active animations'); * } * ``` */ hasAnimations(e) { return e.getAnimations().length > 0; }, /** * Pauses all animations on an element * @param element - The HTML element to pause animations on * @example * ```typescript * const element = document.querySelector('.box'); * AnimationUtils.pauseAnimations(element); * ``` */ pauseAnimations(e) { e.getAnimations().forEach((t) => t.pause()); }, /** * Resumes all paused animations on an element * @param element - The HTML element to resume animations on * @example * ```typescript * const element = document.querySelector('.box'); * AnimationUtils.resumeAnimations(element); * ``` */ resumeAnimations(e) { e.getAnimations().forEach((t) => t.play()); }, /** * Reverses the direction of all animations on an element * @param element - The HTML element to reverse animations on * @example * ```typescript * const element = document.querySelector('.box'); * AnimationUtils.reverseAnimations(element); * ``` */ reverseAnimations(e) { e.getAnimations().forEach((t) => t.reverse()); }, /** * Stops and removes all animations from an element * @param element - The HTML element to stop animations on * @example * ```typescript * const element = document.querySelector('.box'); * AnimationUtils.stopAnimations(element); * ``` */ stopAnimations(e) { e.getAnimations().forEach((t) => t.cancel()); }, /** * Waits for all animations on an element to complete * @param element - The HTML element to wait for * @returns Promise that resolves when all animations complete * @example * ```typescript * const element = document.querySelector('.box'); * await AnimationUtils.waitForAnimations(element); * console.log('All animations completed'); * ``` */ async waitForAnimations(e) { const t = e.getAnimations(); await Promise.all(t.map((r) => r.finished)); } }, W = { /** * Calculates the average of all numbers in an array * @param arr - Array of numbers to calculate average from * @returns The average of all numbers, or 0 if array is empty * @example * ```typescript * const avg = ArrayUtils.average([1, 2, 3, 4, 5]); // Returns 3 * ``` */ average(e) { return !Array.isArray(e) || e.length === 0 ? 0 : this.sumArray(e) / e.length; }, /** * Compares two arrays for equality by sorting and comparing elements * @param a1 - First array to compare * @param a2 - Second array to compare * @returns True if arrays contain the same elements in any order * @example * ```typescript * const equal = ArrayUtils.arrayEquals([1, 2, 3], [3, 2, 1]); // Returns true * ``` */ arrayEquals(e, t) { if (!Array.isArray(e) || !Array.isArray(t) || e.length !== t.length) return !1; const r = [...e].sort(this.sortCompare), n = [...t].sort(this.sortCompare); return r.every((a, o) => a === n[o]); }, /** * Splits an array into chunks of specified size * @param arr - Array to split into chunks * @param size - Size of each chunk (must be positive) * @returns Array of arrays, each of size 'size' * @throws Error if size is not positive */ chunks(e, t) { if (!Number.isInteger(t) || t <= 0) throw new Error("Chunk size must be a positive integer"); const r = []; for (let n = 0; n < e.length; n += t) r.push(e.slice(n, n + t)); return r; }, /** * Compares two arrays for equality based on the given options * @param arr1 - First array to compare * @param arr2 - Second array to compare * @param options - Comparison options * @returns True if arrays are equal based on the options */ compareArrays(e, t, r = {}) { if (!Array.isArray(e) || !Array.isArray(t)) return !1; const { ordered: n = !1, deep: a = !1 } = r; if (e.length !== t.length) return !1; if (n) return e.every( (o, s) => a ? this.deepEqual(o, t[s]) : o === t[s] ); { const o = new Array(t.length).fill(!1); return e.every((s) => { const i = t.findIndex( (c, u) => !o[u] && (a ? this.deepEqual(s, c) : s === c) ); return i === -1 ? !1 : (o[i] = !0, !0); }); } }, /** * Returns a new array with unique values * @param array - Array to make unique * @returns Array with duplicate values removed */ deduplicate(e) { return Array.isArray(e) ? Array.from(new Set(e)) : []; }, /** * Deep equality comparison between two values * @param a - First value * @param b - Second value * @returns True if values are deeply equal */ deepEqual(e, t) { if (e === t) return !0; if (typeof e != typeof t) return !1; if (typeof e == "object" && e !== null && t !== null) { if (Array.isArray(e) !== Array.isArray(t)) return !1; if (Array.isArray(e)) return e.length !== t.length ? !1 : e.every((a, o) => this.deepEqual(a, t[o])); const r = Object.keys(e), n = Object.keys(t); return r.length !== n.length ? !1 : r.every( (a) => this.deepEqual( e[a], t[a] ) ); } return !1; }, /** * Returns elements from array a that are not in array b based on id property * @param a - First array * @param b - Second array * @param getId - Optional function to extract id from items * @returns Array of elements from a that are not in b */ difference(e, t, r = (n) => n.id) { const n = new Set(t.map(r)); return e.filter((a) => !n.has(r(a))); }, /** * Returns the maximum value in an array * @param arr - Array of comparable values * @returns Maximum value or undefined if array is empty */ findMax(e) { if (!(!Array.isArray(e) || e.length === 0)) return e.reduce((t, r) => r > t ? r : t, e[0]); }, /** * Returns the minimum value in an array * @param arr - Array of comparable values * @returns Minimum value or undefined if array is empty */ findMin(e) { if (!(!Array.isArray(e) || e.length === 0)) return e.reduce((t, r) => r < t ? r : t, e[0]); }, /** * Finds and updates an item in a collection based on id * @param collection - Collection to update * @param item - Item to update * @returns Updated collection * @throws Error if item is not found */ findAndUpdate(e, t) { const r = e.findIndex((n) => n.id === t.id); if (r === -1) throw new Error(`Item with id ${t.id} not found in collection`); return e.map((n, a) => a === r ? t : n); }, /** * Flattens a nested array to a specified depth * @param arr - Array to flatten * @param depth - Maximum depth to flatten (default: Infinity) * @returns Flattened array */ flatten(e, t = 1 / 0) { return t <= 0 ? e.slice() : Array.isArray(e) ? e.reduce((r, n) => (Array.isArray(n) ? r.push(...this.flatten(n, t - 1)) : r.push(n), r), []) : [e]; }, /** * Groups array elements by a key or function * @param arr - Array to group * @param keyOrFn - Key to group by or function that returns the group key * @returns Object with grouped arrays */ groupBy(e, t) { return Array.isArray(e) ? e.reduce((r, n) => { const a = typeof t == "function" ? t(n) : String(n[t]); return r[a] = r[a] || [], r[a].push(n), r; }, {}) : {}; }, /** * Checks if two arrays have any common elements * @param array1 - First array * @param array2 - Second array * @returns True if arrays share at least one common element */ hasCommonElement(e, t) { if (!e?.length || !t?.length) return !1; const [r, n] = e.length < t.length ? [e, t] : [t, e], a = new Set(r); return n.some((o) => a.has(o)); }, /** * Returns the intersection of two arrays * @param arr1 - First array * @param arr2 - Second array * @returns Array containing elements present in both arrays */ intersection(e, t) { if (!Array.isArray(e) || !Array.isArray(t)) return []; const r = new Set(t); return e.filter((n) => r.has(n)); }, /** * Moves an item from one position to another in an array * @param arr - Array to modify * @param from - Source index * @param to - Destination index * @returns New array with item moved * @throws Error if indices are out of bounds */ moveItem(e, t, r) { if (!Array.isArray(e)) throw new Error("First argument must be an array"); if (t < 0 || t >= e.length) throw new Error(`Source index ${t} is out of bounds`); if (r < 0 || r >= e.length) throw new Error(`Destination index ${r} is out of bounds`); const n = [...e], [a] = n.splice(t, 1); return n.splice(r, 0, a), n; }, /** * Returns a random element from the array * @param arr - Array to get element from * @returns Random element or undefined if array is empty */ random(e) { if (!(!Array.isArray(e) || e.length === 0)) return e[Math.floor(Math.random() * e.length)]; }, /** * Removes elements from an array that match a predicate * @param arr - Array to remove elements from * @param predicate - Function that returns true for elements to remove * @returns New array with matching elements removed */ remove(e, t) { return Array.isArray(e) ? e.filter((r) => !t(r)) : []; }, /** * Returns a new array with elements in random order * @param arr - Array to shuffle * @returns New array with elements in random order */ shuffle(e) { if (!Array.isArray(e)) return []; const t = [...e]; for (let r = t.length - 1; r > 0; r--) { const n = Math.floor(Math.random() * (r + 1)); [t[r], t[n]] = [t[n], t[r]]; } return t; }, /** * Compares two values for sorting * @param x - First value to compare * @param y - Second value to compare * @returns -1 if x < y, 0 if equal, 1 if x > y */ sortCompare(e, t) { const r = ["string", "number", "bool"]; return typeof e != typeof t ? r.indexOf(typeof t) - r.indexOf(typeof e) : e === t ? 0 : e > t ? 1 : -1; }, /** * Returns the sum of all numbers in an array * @param arr - Array of numbers * @returns Sum of all numbers */ sumArray(e) { return Array.isArray(e) ? e.reduce((t, r) => t + (Number(r) || 0), 0) : 0; }, /** * Returns the first n elements of an array * @param arr - Array to get elements from * @param n - Number of elements to get (default: 1) * @returns Array containing the first n elements */ takeFirst(e, t = 1) { return Array.isArray(e) ? e.slice(0, t) : []; }, /** * Returns the last n elements of an array * @param arr - Array to get elements from * @param n - Number of elements to get (default: 1) * @returns Array containing the last n elements */ takeLast(e, t = 1) { return Array.isArray(e) ? e.slice(-t) : []; }, /** * Returns the union of multiple arrays * @param arrays - Arrays to union * @returns Array containing unique elements from all arrays */ union(...e) { return this.deduplicate(e.flat()); } }, T = (e) => { const { r: t, g: r, b: n } = e; if (t < 0 || t > 255 || r < 0 || r > 255 || n < 0 || n > 255) throw new Error("RGB values must be between 0 and 255"); }, B = (e) => { const { h: t, s: r, l: n } = e; if (t < 0 || t > 360 || r < 0 || r > 100 || n < 0 || n > 100) throw new Error( "HSL values must be between 0-360 for hue and 0-100 for saturation and lightness" ); }, F = (e) => { const { c: t, m: r, y: n, k: a } = e; if (t < 0 || t > 100 || r < 0 || r > 100 || n < 0 || n > 100 || a < 0 || a > 100) throw new Error("CMYK values must be between 0 and 100"); }, Y = { /** * Adjusts the brightness of a color * @param color - RGB color values * @param amount - Amount to adjust (-100 to 100) * @returns Adjusted RGB color values */ adjustBrightness(e, t) { T(e); const r = (n) => Math.max(0, Math.min(255, n + t)); return { r: r(e.r), g: r(e.g), b: r(e.b) }; }, /** * Adjusts the saturation of a color by a specified amount * @param color - RGB color values * @param amount - Amount to adjust (-100 to 100) * @returns Adjusted RGB color values */ adjustSaturation(e, t) { T(e); const r = this.rgbToHsl(e); return r.s = Math.max(0, Math.min(100, r.s + t)), this.hslToRgb(r); }, /** * Blends two colors together * @param color1 - First RGB color * @param color2 - Second RGB color * @param ratio - Blend ratio (0 to 1) * @returns Blended RGB color */ blendColors(e, t, r) { if (T(e), T(t), r < 0 || r > 1) throw new Error("Blend ratio must be between 0 and 1"); const n = (a, o) => Math.round(a * (1 - r) + o * r); return { r: n(e.r, t.r), g: n(e.g, t.g), b: n(e.b, t.b) }; }, /** * Converts a CMYK color to RGB * @param cmyk - CMYK color values * @returns RGB color values */ cmykToRgb(e) { F(e); const { c: t, m: r, y: n, k: a } = e, o = 255 * (1 - t / 100) * (1 - a / 100), s = 255 * (1 - r / 100) * (1 - a / 100), i = 255 * (1 - n / 100) * (1 - a / 100); return { r: Math.round(o), g: Math.round(s), b: Math.round(i) }; }, /** * Calculates the contrast ratio between two colors * @param color1 - First RGB color * @param color2 - Second RGB color * @returns Contrast ratio (1 to 21) */ contrastRatio(e, t) { T(e), T(t); const r = (i) => { const [c, u, m] = [i.r, i.g, i.b].map((h) => (h = h / 255, h <= 0.03928 ? h / 12.92 : Math.pow((h + 0.055) / 1.055, 2.4))); return 0.2126 * c + 0.7152 * u + 0.0722 * m; }, n = r(e), a = r(t), o = Math.max(n, a), s = Math.min(n, a); return (o + 0.05) / (s + 0.05); }, /** * Converts a hex color to RGB * @param hex - Hex color string (e.g., "#FF0000") * @returns RGB color values */ hexToRgb(e) { if (!this.isValidHexColor(e)) throw new Error("Invalid hex color format"); const t = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e); if (!t) throw new Error("Invalid hex color"); return { r: parseInt(t[1], 16), g: parseInt(t[2], 16), b: parseInt(t[3], 16) }; }, /** * Converts an HSL color to RGB * @param hsl - HSL color values * @returns RGB color values */ hslToRgb(e) { B(e); const { h: t, s: r, l: n } = e, a = r / 100, o = n / 100, s = (1 - Math.abs(2 * o - 1)) * a, i = s * (1 - Math.abs(t / 60 % 2 - 1)), c = o - s / 2; let u = 0, m = 0, h = 0; return t >= 0 && t < 60 ? (u = s, m = i, h = 0) : t >= 60 && t < 120 ? (u = i, m = s, h = 0) : t >= 120 && t < 180 ? (u = 0, m = s, h = i) : t >= 180 && t < 240 ? (u = 0, m = i, h = s) : t >= 240 && t < 300 ? (u = i, m = 0, h = s) : t >= 300 && t < 360 && (u = s, m = 0, h = i), { r: Math.round((u + c) * 255), g: Math.round((m + c) * 255), b: Math.round((h + c) * 255) }; }, /** * Inverts a color * @param color - RGB color values * @returns Inverted RGB color values */ invertColor(e) { return T(e), { r: 255 - e.r, g: 255 - e.g, b: 255 - e.b }; }, /** * Validates if a string is a valid hex color * @param hex - Hex color string to validate * @returns True if the string is a valid hex color */ isValidHexColor(e) { return /^#?([a-f\d]{3}|[a-f\d]{6})$/i.test(e); }, /** * Generates a random color in the specified format * @param type - Color format (rgb, hex, hsl, or cmyk) * @returns Random color string in the specified format */ randomColor(e = "hex") { const t = (r) => Math.floor(Math.random() * r); switch (e) { case "rgb": const r = t(256), n = t(256), a = t(256); return `rgb(${r}, ${n}, ${a})`; case "hex": const o = t(256), s = t(256), i = t(256); return `#${[o, s, i].map((A) => A.toString(16).padStart(2, "0")).join("")}`; case "hsl": const c = t(361), u = t(101), m = t(101); return `hsl(${c}, ${u}%, ${m}%)`; case "cmyk": const h = t(101), d = t(101), f = t(101), w = t(101); return `cmyk(${h}%, ${d}%, ${f}%, ${w}%)`; default: const g = t(256), M = t(256), E = t(256); return `#${[g, M, E].map((A) => A.toString(16).padStart(2, "0")).join("")}`; } }, /** * Converts RGB color to CMYK * @param rgb - RGB color values * @returns CMYK color values */ rgbToCmyk(e) { T(e); const { r: t, g: r, b: n } = e, a = t / 255, o = r / 255, s = n / 255, i = 1 - Math.max(a, o, s), c = (1 - a - i) / (1 - i) || 0, u = (1 - o - i) / (1 - i) || 0, m = (1 - s - i) / (1 - i) || 0; return { c: Math.round(c * 100), m: Math.round(u * 100), y: Math.round(m * 100), k: Math.round(i * 100) }; }, /** * Converts RGB color to hex * @param rgb - RGB color values * @returns Hex color string */ rgbToHex(e) { T(e); const { r: t, g: r, b: n } = e; return "#" + [t, r, n].map((a) => a.toString(16).padStart(2, "0")).join(""); }, /** * Converts RGB color to HSL * @param rgb - RGB color values * @returns HSL color values */ rgbToHsl(e) { T(e); const { r: t, g: r, b: n } = e, a = t / 255, o = r / 255, s = n / 255, i = Math.max(a, o, s), c = Math.min(a, o, s); let u = 0, m = 0; const h = (i + c) / 2; if (i !== c) { const d = i - c; switch (m = h > 0.5 ? d / (2 - i - c) : d / (i + c), i) { case a: u = (o - s) / d + (o < s ? 6 : 0); break; case o: u = (s - a) / d + 2; break; case s: u = (a - o) / d + 4; break; } u /= 6; } return { h: Math.round(u * 360), s: Math.round(m * 100), l: Math.round(h * 100) }; } }, $ = 1e3, K = $ * 60, j = K * 60, G = j * 24, J = { /** * Adds specified time units to a date. * @param args - Object containing time units to add * @param date - Date to add to (defaults to current date) * @returns New date with added time units * @throws {Error} If any time unit is negative * @example * ```typescript * DateUtils.add({ days: 7, hours: 2 }); // Add 7 days and 2 hours to now * DateUtils.add({ months: 1 }, new Date('2023-01-15')); // Add 1 month to specific date * ``` */ add(e, t = /* @__PURE__ */ new Date()) { this.validateTimeUnits(e); const r = this.convertToDate(t); return this.adjustDate(e, r, 1); }, /** * Internal helper for modifying a date by adding or subtracting time units * @param args - Object containing time units * @param date - Date to modify * @param direction - 1 for addition, -1 for subtraction * @returns New date with modified time units */ adjustDate(e, t, r) { const n = new Date(t.valueOf()); return e.years && n.setFullYear(n.getFullYear() + e.years * r), e.months && n.setMonth(n.getMonth() + e.months * r), e.weeks && n.setDate(n.getDate() + e.weeks * 7 * r), e.days && n.setDate(n.getDate() + e.days * r), e.hours && n.setHours(n.getHours() + e.hours * r), e.minutes && n.setMinutes(n.getMinutes() + e.minutes * r), e.seconds && n.setSeconds(n.getSeconds() + e.seconds * r), n; }, /** * Calculates age based on birth date * @param date - Birth date to calculate age from * @returns Age in years or null if date is invalid */ calculateAge(e) { if (!e) return null; const t = /* @__PURE__ */ new Date(), r = e instanceof Date ? e : new Date(e); let n = t.getFullYear() - r.getFullYear(); const a = t.getMonth() - r.getMonth(), o = t.getDate() - r.getDate(); return (a < 0 || a === 0 && o < 0) && n--, n; }, /** * Calculates the number of days between two dates * @param first - First date * @param second - Second date * @returns Number of days between dates */ calculateDaysBetween(e, t) { const r = this.convertToDate(e), n = this.convertToDate(t), a = Date.UTC(r.getFullYear(), r.getMonth(), r.getDate()), o = Date.UTC(n.getFullYear(), n.getMonth(), n.getDate()); return Math.abs(Math.floor((o - a) / G)); }, /** * Compares two dates and returns the result based on order parameter * @param a - First date to compare * @param b - Second date to compare * @param order - Sort order ('asc' or 'desc') * @returns Comparison result (-1, 0, or 1) */ compareDates(e, t, r = "asc") { const n = this.normalizeDate(e), a = this.normalizeDate(t); if (!n && !a) return 0; if (!n) return r === "asc" ? 1 : -1; if (!a) return r === "asc" ? -1 : 1; const o = n.getTime() - a.getTime(); return r === "asc" ? o : -o; }, /** * Converts various date formats to a Date object * @param dateStr - Date to convert (Date, number, or string) * @returns Date object * @throws Error if date string is invalid */ convertToDate(e) { if (e instanceof Date) return new Date(e.valueOf()); if (typeof e == "number") return e.toString().length === 10 ? new Date(e * $) : new Date(e); if (typeof e == "string") { const t = new Date(e); if (isNaN(t.getTime())) throw new Error(`Invalid date string: ${e}`); return t; } throw new Error("Invalid date argument"); }, /** * Copies time from one date to another * @param fromDate - Source date to copy time from * @param toDate - Target date to copy time to * @returns New date with copied time */ copyTimeToDate(e, t) { const r = this.convertToDate(e), n = this.convertToDate(t); return n.setHours( r.getHours(), r.getMinutes(), r.getSeconds(), r.getMilliseconds() ), n; }, /** * Returns the age in years between two dates * @param birthDate - Birth date * @param referenceDate - Reference date (defaults to current date) * @returns Age in years */ getAge(e, t = /* @__PURE__ */ new Date()) { const r = this.convertToDate(e), n = this.convertToDate(t); let a = n.getFullYear() - r.getFullYear(); const o = n.getMonth() - r.getMonth(); return (o < 0 || o === 0 && n.getDate() < r.getDate()) && a--, a; }, /** * Returns the number of days in a given month * @param year - Year to check * @param month - Month to check (0-11) * @returns Number of days in the month */ getDaysInMonth(e, t) { return new Date(e, t + 1, 0).getDate(); }, /** * Returns the number of days between two dates, inclusive * @param startDate - Start date * @param endDate - End date * @returns Number of days between dates, inclusive */ getDaysBetweenInclusive(e, t) { return this.calculateDaysBetween(e, t) + 1; }, /** * Returns the first day of the month * @param date - Date to get first day of month for (defaults to current date) * @returns Date set to first day of month */ getFirstDayOfMonth(e = /* @__PURE__ */ new Date()) { const t = this.convertToDate(e); return t.setDate(1), this.getStartOfDay(t); }, /** * Returns the first day of the quarter * @param date - Date to get first day of quarter for (defaults to current date) * @returns Date set to first day of quarter */ getFirstDayOfQuarter(e = /* @__PURE__ */ new Date()) { const t = this.convertToDate(e), r = this.getQuarter(t); return t.setMonth((r - 1) * 3, 1), this.getStartOfDay(t); }, /** * Returns the first day of the week (Sunday) * @param date - Date to get first day of week for (defaults to current date) * @returns Date set to first day of week */ getFirstDayOfWeek(e = /* @__PURE__ */ new Date()) { const t = this.convertToDate(e), r = t.getDay(); return t.setDate(t.getDate() - r), this.getStartOfDay(t); }, /** * Returns the first day of the year * @param date - Date to get first day of year for (defaults to current date) * @returns Date set to first day of year */ getFirstDayOfYear(e = /* @__PURE__ */ new Date()) { const t = this.convertToDate(e); return t.setMonth(0, 1), this.getStartOfDay(t); }, /** * Returns the end of the day (23:59:59.999) * @param date - Date to get end of day for (defaults to current date) * @returns Date set to end of day */ getEndOfDay(e) { const t = this.convertToDate(e || /* @__PURE__ */ new Date()); return t.setHours(23, 59, 59, 999), t; }, /** * Returns the last day of the month * @param date - Date to get last day of month for (defaults to current date) * @returns Date set to last day of month */ getLastDayOfMonth(e = /* @__PURE__ */ new Date()) { const t = this.convertToDate(e); return t.setMonth(t.getMonth() + 1), t.setDate(0), this.getEndOfDay(t); }, /** * Returns the last day of the quarter * @param date - Date to get last day of quarter for (defaults to current date) * @returns Date set to last day of quarter */ getLastDayOfQuarter(e = /* @__PURE__ */ new Date()) { const t = this.convertToDate(e), r = this.getQuarter(t); return t.setMonth(r * 3, 0), this.getEndOfDay(t); }, /** * Returns the last day of the week (Saturday) * @param date - Date to get last day of week for (defaults to current date) * @returns Date set to last day of week */ getLastDayOfWeek(e = /* @__PURE__ */ new Date()) { const t = this.convertToDate(e), r = t.getDay(); return t.setDate(t.getDate() + (6 - r)), this.getEndOfDay(t); }, /** * Returns the last day of the year * @param date - Date to get last day of year for (defaults to current date) * @returns Date set to last day of year */ getLastDayOfYear(e = /* @__PURE__ */ new Date()) { const t = this.convertToDate(e); return t.setMonth(11, 31), this.getEndOfDay(t); }, /** * Returns the quarter (1-4) of the year * @param date - Date to get quarter for (defaults to current date) * @returns Quarter number */ getQuarter(e = /* @__PURE__ */ new Date()) { const t = this.convertToDate(e).getMonth(); return Math.floor(t / 3) + 1; }, /** * Returns the start of the day (00:00:00.000) * @param date - Date to get start of day for (defaults to current date) * @returns Date set to start of day */ getStartOfDay(e) { const t = this.convertToDate(e || /* @__PURE__ */ new Date()); return t.setHours(0, 0, 0, 0), t; }, /** * Returns the week number of the year (1-53) * @param date - Date to get week number for (defaults to current date) * @returns Week number */ getWeekNumber(e = /* @__PURE__ */ new Date()) { const t = this.convertToDate(e), r = new Date(t.getFullYear(), 0, 1), n = (t.getTime() - r.getTime()) / G; return Math.ceil((n + r.getDay() + 1) / 7); }, /** * Checks if a date is after another date * @param date1 - First date to compare * @param date2 - Second date to compare * @returns True if date1 is after date2 */ isAfter(e, t) { return this.convertToDate(e).valueOf() > this.convertToDate(t).valueOf(); }, /** * Checks if a date is before another date * @param date1 - First date to compare * @param date2 - Second date to compare * @returns True if date1 is before date2 */ isBefore(e, t) { return this.convertToDate(e).valueOf() < this.convertToDate(t).valueOf(); }, /** * Checks if a date is in the past * @param date - Date to check * @returns True if date is in the past */ isDateInThePast(e) { return this.convertToDate(e) <= /* @__PURE__ */ new Date(); }, /** * Checks if two dates are the same day * @param date1 - First date to compare * @param date2 - Second date to compare * @returns True if dates are the same day */ isSameDay(e, t) { const r = this.convertToDate(e), n = this.convertToDate(t); return r.getDate() === n.getDate() && r.getMonth() === n.getMonth() && r.getFullYear() === n.getFullYear(); }, /** * Checks if two dates have the same time * @param date1 - First date to compare * @param date2 - Second date to compare * @returns True if dates have the same time */ isSameTime(e, t) { return this.convertToDate(e).valueOf() === this.convertToDate(t).valueOf(); }, /** * Checks if a date is today * @param date - Date to check * @returns True if date is today */ isToday(e) { const t = this.convertToDate(e); return !t || t.toLocaleDateString() === (/* @__PURE__ */ new Date()).toLocaleDateString(); }, /** * Checks if a date is tomorrow * @param date - Date to check * @returns True if date is tomorrow */ isTomorrow(e) { const t = /* @__PURE__ */ new Date(); return t.setDate(t.getDate() + 1), this.isSameDay(e, t); }, /** * Checks if a date is valid * @param date - Date to check * @returns True if date is valid */ isValidDate(e) { try { return !Number.isNaN(this.convertToDate(e).getTime()); } catch { return !1; } }, /** * Checks if a date falls on a weekday * @param date - Date to check * @returns True if the date is a weekday */ isWeekday(e) { return !this.isWeekend(e); }, /** * Checks if a date falls on a weekend * @param date - Date to check * @returns True if the date is a weekend */ isWeekend(e) { const t = this.convertToDate(e).getUTCDay(); return t === 0 || t === 6; }, /** * Checks if a date is yesterday * @param date - Date to check * @returns True if date is yesterday */ isYesterday(e) { const t = /* @__PURE__ */ new Date(); return t.setDate(t.getDate() - 1), this.isSameDay(e, t); }, /** * Normalizes a date to UTC midnight * @param date - Date to normalize * @returns Normalized date or null if invalid */ normalizeDate(e) { if (!e) return null; try { if (typeof e == "string") { const r = e.split("/"); if (r.length === 2 && r[0].length === 2 && r[1].length === 4) { const n = parseInt(r[0], 10), a = parseInt(r[1], 10); if (!isNaN(n) && !isNaN(a) && n >= 1 && n <= 12) return new Date(Date.UTC(a, n - 1, 1)); } } const t = this.convertToDate(e); if (!isNaN(t.valueOf())) return new Date( Date.UTC( t.getUTCFullYear(), t.getUTCMonth(), t.getUTCDate() ) ); } catch { } return null; }, /** * Subtracts specified time units from a date * @param args - Object containing time units to subtract * @param date - Date to subtract from (defaults to current date) * @returns New date with subtracted time units * @throws Error if any time unit is negative */ subtract(e, t = /* @__PURE__ */ new Date()) { this.validateTimeUnits(e); const r = this.convertToDate(t); return this.adjustDate(e, r, -1); }, /** * Converts a date to a local ISO string * @param date - Date to convert * @returns Local ISO string representation */ toLocalISOString(e) { const t = (r) => String(r).padStart(2, "0"); return `${e.getFullYear()}-${t(e.getMonth() + 1)}-${t( e.getDate() )}T${t(e.getHours())}:${t(e.getMinutes())}:${t( e.getSeconds() )}.${String(e.getMilliseconds()).padStart(3, "0")}`; }, /** * Validates time unit arguments * @param args - Time unit arguments to validate * @throws Error if any time unit is negative */ validateTimeUnits(e) { for (const [t, r] of Object.entries(e)) if (r !== void 0 && r < 0) throw new Error( `Negative values are not allowed for time unit: ${t}` ); } }, Q = { /** * Adds a listener for online/offline status changes * @param callback - Function to call when status changes * @returns Function to remove the listener */ addOnlineStatusListener(e) { const t = () => e(navigator.onLine); return window.addEventListener("online", t), window.addEventListener("offline", t), () => { window.removeEventListener("online", t), window.removeEventListener("offline", t); }; }, /** * Gets the battery level of the device * @returns Promise resolving to battery level (0-1) or null if not available */ async getBatteryLevel() { if (!("getBattery" in navigator)) return null; try { return (await navigator.getBattery()).level; } catch { return null; } }, /** * Gets the connection type of the device * @returns Connection type or null if not available */ getConnectionType() { return "connection" in navigator && navigator.connection?.effectiveType || null; }, /** * Gets the device type (mobile, tablet, desktop) * @returns Device type */ getDeviceType() { const e = navigator.userAgent; return /(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(e) ? "tablet" : /Mobile|Android|iP(hone|od)|IEMobile|BlackBerry|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)/.test( e ) ? "mobile" : "desktop"; }, /** * Gets the number of CPU cores available * @returns Number of CPU cores */ getHardwareConcurrency() { return navigator.hardwareConcurrency || 1; }, /** * Gets the device language * @returns Device language */ getLanguage() { return navigator.language; }, /** * Gets the device memory in GB * @returns Device memory or null if not available */ getMemory() { return "deviceMemory" in navigator && navigator.deviceMemory || null; }, /** * Gets the device orientation * @returns Device orientation */ getOrientation() { return window.innerHeight > window.innerWidth ? "portrait" : "landscape"; }, /** * Gets the device pixel ratio * @returns Device pixel ratio */ getPixelRatio() { return window.devicePixelRatio || 1; }, /** * Gets the device platform * @returns Device platform */ getPlatform() { return "userAgentData" in navigator ? navigator.userAgentData.platform || "" : navigator.userAgent; }, /** * Gets the screen dimensions * @returns Object containing screen width and height */ getScreenDimensions() { return { width: window.screen.width, height: window.screen.height }; }, /** * Gets the device vendor * @returns Device vendor */ getVendor() { return "userAgentData" in navigator ? navigator.userAgentData.brands[0]?.brand || "" : navigator.userAgent; }, /** * Checks if a string is a valid MAC address * @param mac - MAC address to validate * @returns True if valid, false ot