/** * Applies a function to the value at the given index of an array * * @param index * @param fn * @param collection * @returns Copy of the array with the element at the given index replaced with the result of the function application. */ const adjust = (index, fn, collection) => { if (index >= collection.length) return collection; if (index < 0 && Math.abs(index) > collection.length) return collection; const i = index < 0 ? collection.length + index : index; return Object.assign([], collection, { [i]: fn(collection[i]) }); }; /* eslint-disable no-await-in-loop */ /** * Performs right-to-left function composition with async functions support. * asyncCompose(f, g, h)(x) is equivalent to f(g(h(x))), awaiting each result. * * @param fns - Functions to compose (can be sync or async) * @returns Composed async function that applies functions from right to left */ const asyncCompose = (...fns) => async (value, ...args) => { let result = value; for (let i = fns.length - 1; i >= 0; i -= 1) { result = await fns[i](result, ...args); } return result; }; /** * Capitalize first letter of each word * * @param value - Any string * @returns Capitalized string */ const capitalize = (value) => { if (!value) return value; return value.replace(/(^|\s)\S/g, (l) => l.toUpperCase()); }; /** * Casts value to array * * @template T - The type of the value. * @param value - The value to cast into an array. * @returns The value as-is if already an array, otherwise wrapped in an array. */ const castArray = (value) => { return Array.isArray(value) ? value : [value]; }; /** * Performs right-to-left function composition. * compose(f, g, h)(x) is equivalent to f(g(h(x))) * * @param fns - Functions to compose * @returns Composed function that applies functions from right to left */ const compose = (...fns) => (value, ...args) => { let result = value; for (let i = fns.length - 1; i >= 0; i -= 1) { result = fns[i](result, ...args); } return result; }; function dropLast(value) { return value.slice(0, -1); } /** * Applies a set of transformations to an object and returns a new object with the transformed values. * * @example * evolve({ count: (n) => n + 1 }, { name: 'item', count: 5 }) * // => { name: 'item', count: 6 } * * @param transformations - The transformations to apply * @param object - The object to transform * @returns The transformed object */ function evolve(transformations, object) { const result = Object.create(null); const keys = Object.keys(object); for (let i = 0; i < keys.length; i += 1) { const key = keys[i]; if (key === '__proto__' || key === 'constructor' || key === 'prototype') { continue; } const transformation = transformations[key]; if (typeof transformation === 'function') { result[key] = transformation(object[key]); } else { result[key] = object[key]; } } return result; } /** * Checks if a value is null or undefined. * * @example * isNil(null) // => true * isNil(undefined) // => true * isNil(0) // => false * * @param value - The value to check * @returns True if the value is null or undefined, false otherwise */ const isNil = (value) => value === null || value === undefined; /** * Retrieves the value at a given path from an object. * * @example * get({ a: { b: 1 } }, ['a', 'b'], 0) // => 1 * get({ a: { b: 1 } }, ['a', 'c'], 0) // => 0 * * @param target - The object to retrieve the value from * @param path - The path of the value to retrieve * @param defaultValue - The default value to return if the path does not exist * @returns The value at the given path, or the default value if the path does not exist */ const get = (target, path, defaultValue) => { if (isNil(target)) return defaultValue; const _path = castArray(path); let result = target; for (let i = 0; i < _path.length; i += 1) { if (isNil(result)) return defaultValue; result = result[_path[i]]; } return isNil(result) ? defaultValue : result; }; function last(value) { return value === '' ? '' : value[value.length - 1]; } /** * Maps over the values of an object and applies a function to each value. * * @example * mapValues({ a: 1, b: 2 }, (v) => v * 2) // => { a: 2, b: 4 } * * @param object - The object to map over * @param fn - The function to apply to each value * @returns A new object with the mapped values */ const mapValues = (object, fn) => { const result = {}; const entries = Object.entries(object); for (let i = 0; i < entries.length; i += 1) { const [key, value] = entries[i]; if (key === '__proto__' || key === 'constructor' || key === 'prototype') { continue; } result[key] = fn(value, key, i); } return result; }; const PERCENT_REGEX = /(-?\d+\.?\d*)%/; /** * Parses a percentage string and returns both the numeric value and decimal percent. * * @example * matchPercent('50%') // => { value: 50, percent: 0.5 } * matchPercent('-25%') // => { value: -25, percent: -0.25 } * matchPercent('abc') // => null * * @param value - The value to parse * @returns Object with value and percent, or null if not a valid percentage */ const matchPercent = (value) => { const match = PERCENT_REGEX.exec(`${value}`); if (match) { const numericValue = parseFloat(match[1]); const percent = numericValue / 100; return { percent, value: numericValue }; } return null; }; /** * Creates a new object by omitting specified keys from the original object. * * @example * omit('b', { a: 1, b: 2, c: 3 }) // => { a: 1, c: 3 } * omit(['a', 'c'], { a: 1, b: 2, c: 3 }) // => { b: 2 } * * @param keys - The key or keys to omit * @param object - The original object * @returns The new object without the omitted keys */ const omit = (keys, object) => { const _keys = castArray(keys); const copy = { ...object }; for (let i = 0; i < _keys.length; i += 1) { delete copy[_keys[i]]; } return copy; }; /** * Picks the specified keys from an object and returns a new object with only those keys. * * @example * pick(['a', 'c'], { a: 1, b: 2, c: 3 }) // => { a: 1, c: 3 } * pick(['x'], { a: 1, b: 2 }) // => {} * * @param keys - The keys to pick from the object * @param object - The object to pick the keys from * @returns A new object with only the picked keys */ const pick = (keys, object) => { const result = {}; for (let i = 0; i < keys.length; i += 1) { const key = keys[i]; if (key === '__proto__' || key === 'constructor' || key === 'prototype') { continue; } if (key in object) result[key] = object[key]; } return result; }; /** * Repeats an element a specified number of times. * * @example * repeat('a', 3) // => ['a', 'a', 'a'] * repeat(0, 4) // => [0, 0, 0, 0] * repeat('x') // => [] * * @param element - Element to be repeated * @param length - Number of times to repeat element (default: 0) * @returns Array with the element repeated */ const repeat = (element, length = 0) => { const result = new Array(length); for (let i = 0; i < length; i += 1) { result[i] = element; } return result; }; /** * Returns a new array with elements in reverse order. Does not mutate the original. * * @example * reverse([1, 2, 3]) // => [3, 2, 1] * reverse(['a', 'b']) // => ['b', 'a'] * * @param array - Array to be reversed * @returns New array with elements reversed */ const reverse = (array) => array.slice().reverse(); /** * Converts the first character of a string to uppercase. Does not affect other characters. * * @example * upperFirst('hello') // => 'Hello' * upperFirst('hELLO') // => 'HELLO' * upperFirst('') // => '' * upperFirst(null) // => null * * @param value - The string to transform * @returns String with first character uppercased, or the original value if null/undefined/empty */ const upperFirst = (value) => { if (!value) return value; return (value.charAt(0).toUpperCase() + value.slice(1)); }; /** * Returns a new array excluding the specified values. * * @example * without([2, 4], [1, 2, 3, 4, 5]) // => [1, 3, 5] * without(['b'], ['a', 'b', 'c']) // => ['a', 'c'] * * @param exclude - Values to exclude from the array * @param array - Array to filter * @returns A new array without the excluded values */ const without = (exclude, array) => { const result = []; for (let i = 0; i < array.length; i += 1) { const value = array[i]; if (!exclude.includes(value)) result.push(value); } return result; }; /** * Parse a string or number to a float. Non-string values pass through unchanged. * * @example * parseFloat('3.14') // => 3.14 * parseFloat(42) // => 42 * parseFloat('10px') // => 10 * parseFloat(null) // => null * * @param value - The value to parse * @returns Parsed float for strings, original value otherwise */ const parseFloat$1 = (value) => { return (typeof value === 'string' ? Number.parseFloat(value) : value); }; export { adjust, asyncCompose, capitalize, castArray, compose, dropLast, evolve, get, isNil, last, mapValues, matchPercent, omit, parseFloat$1 as parseFloat, pick, repeat, reverse, upperFirst, without };