minsky-kit
Version:
Kit of components for MINSKY web agency
208 lines (181 loc) • 5.26 kB
JavaScript
/**
* Deeply merges source objects into the target object.
*
* Behavior:
* - plain objects are merged recursively
* - arrays are concatenated
* - primitive values are overwritten by the latest source
*
* @param {Object} target - object to merge into
* @param {...Object} sources - source objects
* @returns {Object} merged target object
*/
export function _merge(target, ...sources) {
if (!isObject(target) && !Array.isArray(target)) {
target = {};
}
for (const source of sources) {
if (!isObject(source) && !Array.isArray(source)) continue;
for (const key of Object.keys(source)) {
const srcValue = source[key];
const tgtValue = target[key];
// merge arrays by concatenation
if (Array.isArray(srcValue)) {
if (Array.isArray(tgtValue)) {
target[key] = [...tgtValue, ...srcValue];
} else {
target[key] = [...srcValue];
}
continue;
}
// merge plain objects recursively
if (isObject(srcValue)) {
if (!isObject(tgtValue)) {
target[key] = {};
}
target[key] = _merge(target[key], srcValue);
continue;
}
// overwrite primitives
target[key] = srcValue;
}
}
return target;
}
/**
* Removes all elements from an array that match any value in the given values array.
* Modifies the original array in place.
*
* @param {Array} array - the array to modify
* @param {Array} values - values to remove from the array
* @returns {Array} - the modified array
*/
export function _pullAll(array, values) {
if (!Array.isArray(array) || !Array.isArray(values)) {
return array;
}
let index = 0;
while (index < array.length) {
if (values.includes(array[index])) {
array.splice(index, 1);
} else {
index++;
}
}
return array;
}
/**
* Creates a shallow clone of a value.
* - Primitives and functions are returned as-is.
* - Arrays are shallow-copied.
* - Plain objects are shallow-copied.
*
* @param {*} value - value to clone
* @returns {*} - cloned value
*/
export function _clone(value) {
// Primitives and functions are returned directly
if (value === null || typeof value !== 'object') {
return value;
}
// Array clone
if (Array.isArray(value)) {
return value.slice();
}
// Object clone (shallow)
const result = {};
Object.keys(value).forEach(key => {
result[key] = value[key];
});
return result;
}
/**
* Converts a string to kebab-case.
* - Replaces non-alphanumeric characters with spaces.
* - Inserts spaces between lowercase-uppercase transitions.
* - Converts to lowercase and joins with hyphens.
*
* @param {string} str - input string
* @returns {string} - kebab-case string
*/
export function _kebabCase(str) {
if (typeof str !== 'string') return '';
return str
// Replace separators and transitions between lowercase-uppercase
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
// Replace non-alphanumeric characters with space
.replace(/[^a-zA-Z0-9]+/g, ' ')
// Trim spaces
.trim()
// Split into words
.split(/\s+/)
// Convert to lowercase
.map(word => word.toLowerCase())
// Join with hyphen
.join('-');
}
/**
* Returns an array of an object's own enumerable property names.
* Safe for null/undefined objects.
*
* @param {Object} obj - object to get keys from
* @returns {string[]} - array of keys
*/
export const _keys = obj => obj == null ? [] : Object.keys(Object(obj));
/**
* Returns a new object containing only the specified keys from the source object.
* Accepts a single key or an array of keys.
* Safe for null/undefined objects.
*
* @param {Object} obj - source object
* @param {string[] | string} keys - key(s) to pick
* @returns {Object} - object with selected keys
*/
export function _pick(obj, keys) {
if (obj == null) return {};
if (!Array.isArray(keys)) keys = [keys];
const result = {};
for (const key of keys) {
if (key in obj) {
result[key] = obj[key];
}
}
return result;
}
/**
* Recursively assigns default values from source objects to the target object.
* - Only sets a key if the target's value is undefined.
* - Merges nested objects recursively.
*
* @param {Object} target - target object
* @param {...Object} sources - source objects
* @returns {Object} - target object with defaults applied
*/
export function _defaultsDeep(target, ...sources) {
if (target == null) target = {};
for (const source of sources) {
if (source == null) continue;
for (const key of Object.keys(source)) {
const srcVal = source[key];
const tgtVal = target[key];
if (tgtVal === undefined) {
// copy value if undefined
target[key] = srcVal;
} else if (isObject(tgtVal) && isObject(srcVal)) {
// recurse into nested objects
_defaultsDeep(tgtVal, srcVal);
}
// otherwise, keep existing target value
}
}
return target;
}
/**
* Helper to check if a value is a plain object (non-null, non-array).
*
* @param {*} val - value to check
* @returns {boolean} - true if plain object
*/
function isObject(val) {
return val !== null && typeof val === "object" && !Array.isArray(val);
}