utils_merge.mjs

/**
## /utils/merge

Export the default mergeDeep method to mapp.utils{}.

@module /utils/merge
*/

/**
@function mergeDeep

@description
The mergeDeep utility method will set the source object's own enumerable string-keyed property values on a target object.

mergeDeep will call itself recursively allowing to merge nested objects.

An empty target object can be provided to effectively clone a source object.

Instances of HTMLElement objects will be ignored by the merge method.

Arrays will not be merged. The source array will overwrite a target array.

@param {Object} target 
@param  {Spread} sources The sources param is spread into an array.
*/
export default function mergeDeep(target, ...sources) {

  // No sources to merge.
  if (!sources.length) {
    return target;
  }

  // Shift first source out of sources array.
  const source = sources.shift();

  // target & source are both objects.
  if (!isObject(target) || !isObject(source)) {

    return mergeDeep(target, ...sources);
  }

  // Iterate over object keys in source.
  for (const key in source) {

    if (Object.getPrototypeOf(source) !== Object.getPrototypeOf({})) continue;

    if (key === '__proto__' || key === 'constructor') continue;

    if (source[key] instanceof HTMLElement) {
      console.warn(source[key])

      // source[key] is object with potential nesting.
    } else if (isObject(source[key])) {

      // Target key must be an object.
      target[key] ??= {}

      // Call recursive merge for target key object.
      mergeDeep(target[key], source[key]);

    } else {

      // source[key] could be null, true, false, or Array object
      target[key] = source[key]
    }
  }

  return mergeDeep(target, ...sources);
}

/**
@function isObject

@description
The method checks whether item param is an object, excluding true, false, null, Array objects.

@param {Any} item 

@return {Boolean} Return boolean whether item param is an object.
*/
function isObject(item) {

  if (item === true) return false;

  if (item === false) return false;

  if (item === null) return false;

  if (Array.isArray(item)) return false;

  if (typeof item === 'object') return true;
}