Source: editor/components/jgraduate/Util.js

/**
 * @param {any} obj
 * @returns {any}
 */
export function findPos (obj) {
  let curleft = 0
  let curtop = 0
  if (obj.offsetParent) {
    do {
      curleft += obj.offsetLeft
      curtop += obj.offsetTop
    // eslint-disable-next-line no-cond-assign
    } while (obj = obj.offsetParent)
    return { left: curleft, top: curtop }
  }
  return { left: curleft, top: curtop }
}

export function isObject (item) {
  return (item && typeof item === 'object' && !Array.isArray(item))
}

export function mergeDeep (target, source) {
  const output = Object.assign({}, target)
  if (isObject(target) && isObject(source)) {
    Object.keys(source).forEach((key) => {
      if (isObject(source[key])) {
        if (!(key in target)) { Object.assign(output, { [key]: source[key] }) } else { output[key] = mergeDeep(target[key], source[key]) }
      } else {
        Object.assign(output, { [key]: source[key] })
      }
    })
  }
  return output
}

/**
 * Get the closest matching element up the DOM tree.
 * @param  {Element} elem     Starting element
 * @param  {String}  selector Selector to match against (class, ID, data attribute, or tag)
 * @return {Boolean|Element}  Returns null if not match found
 */
export function getClosest (elem, selector) {
  const firstChar = selector.charAt(0)
  const supports = 'classList' in document.documentElement
  let attribute; let value
  // If selector is a data attribute, split attribute from value
  if (firstChar === '[') {
    selector = selector.substr(1, selector.length - 2)
    attribute = selector.split('=')
    if (attribute.length > 1) {
      value = true
      attribute[1] = attribute[1].replace(/"/g, '').replace(/'/g, '')
    }
  }
  // Get closest match
  for (; elem && elem !== document && elem.nodeType === 1; elem = elem.parentNode) {
    // If selector is a class
    if (firstChar === '.') {
      if (supports) {
        if (elem.classList.contains(selector.substr(1))) {
          return elem
        }
      } else {
        if (new RegExp('(^|\\s)' + selector.substr(1) + '(\\s|$)').test(elem.className)) {
          return elem
        }
      }
    }
    // If selector is an ID
    if (firstChar === '#') {
      if (elem.id === selector.substr(1)) {
        return elem
      }
    }
    // If selector is a data attribute
    if (firstChar === '[') {
      if (elem.hasAttribute(attribute[0])) {
        if (value) {
          if (elem.getAttribute(attribute[0]) === attribute[1]) {
            return elem
          }
        } else {
          return elem
        }
      }
    }
    // If selector is a tag
    if (elem.tagName.toLowerCase() === selector) {
      return elem
    }
  }
  return null
}

/**
 * Get all DOM element up the tree that contain a class, ID, or data attribute
 * @param  {Node} elem The base element
 * @param  {String} selector The class, id, data attribute, or tag to look for
 * @return {Array} Null if no match
 */
export function getParents (elem, selector) {
  const parents = []
  const firstChar = selector?.charAt(0)
  // Get matches
  for (; elem && elem !== document; elem = elem.parentNode) {
    if (selector) {
      // If selector is a class
      if (firstChar === '.') {
        if (elem.classList.contains(selector.substr(1))) {
          parents.push(elem)
        }
      }
      // If selector is an ID
      if (firstChar === '#') {
        if (elem.id === selector.substr(1)) {
          parents.push(elem)
        }
      }
      // If selector is a data attribute
      if (firstChar === '[') {
        if (elem.hasAttribute(selector.substr(1, selector.length - 1))) {
          parents.push(elem)
        }
      }
      // If selector is a tag
      if (elem.tagName.toLowerCase() === selector) {
        parents.push(elem)
      }
    } else {
      parents.push(elem)
    }
  }
  // Return parents if any exist
  return parents.length ? parents : null
}

export function getParentsUntil (elem, parent, selector) {
  const parents = []
  const parentType = parent?.charAt(0)
  const selectorType = selector?.selector.charAt(0)
  // Get matches
  for (; elem && elem !== document; elem = elem.parentNode) {
    // Check if parent has been reached
    if (parent) {
      // If parent is a class
      if (parentType === '.') {
        if (elem.classList.contains(parent.substr(1))) {
          break
        }
      }
      // If parent is an ID
      if (parentType === '#') {
        if (elem.id === parent.substr(1)) {
          break
        }
      }
      // If parent is a data attribute
      if (parentType === '[') {
        if (elem.hasAttribute(parent.substr(1, parent.length - 1))) {
          break
        }
      }
      // If parent is a tag
      if (elem.tagName.toLowerCase() === parent) {
        break
      }
    }
    if (selector) {
      // If selector is a class
      if (selectorType === '.') {
        if (elem.classList.contains(selector.substr(1))) {
          parents.push(elem)
        }
      }
      // If selector is an ID
      if (selectorType === '#') {
        if (elem.id === selector.substr(1)) {
          parents.push(elem)
        }
      }
      // If selector is a data attribute
      if (selectorType === '[') {
        if (elem.hasAttribute(selector.substr(1, selector.length - 1))) {
          parents.push(elem)
        }
      }
      // If selector is a tag
      if (elem.tagName.toLowerCase() === selector) {
        parents.push(elem)
      }
    } else {
      parents.push(elem)
    }
  }
  // Return parents if any exist
  return parents.length ? parents : null
}