all files / src/hyperscript/hasCssSelector/ hasCssSelector.ts

26.87% Statements 18/67
19.57% Branches 9/46
33.33% Functions 2/6
26.98% Lines 17/63
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112      26×   26×     26×     23×               23×                                   23×                                           23×                                       23×                                              
import { VNode } from '../../'
import { matchesSelector } from './matchesSelector'
 
// tslint:disable-next-line:cyclomatic-complexity
export function hasCssSelector(cssSelector: string, vNode: VNode): boolean {
  cssSelector = cssSelector.trim()
 
  Iif (cssSelector === '*')
    return true
 
  if (cssSelector.indexOf(',') > -1) {
    const cssSelectors = cssSelector.split(',').map((str) => str.trim())
 
    for (let i = 0; i < cssSelectors.length; ++i)
      if (hasCssSelector(cssSelectors[i], vNode))
        return true
 
    return false
  } else Iif (cssSelector.indexOf('>') > -1) {
    const [ parentSelector, childSelector ] = splitByLastIndex(cssSelector, '>')
 
    if (!vNode.parent)
      return false
 
    return hasCssSelector(parentSelector, vNode.parent) &&
      hasCssSelector(childSelector, vNode)
  } else Iif (cssSelector.indexOf(' + ') > -1) {
    const [ siblingSelector, selector ] = splitByLastIndex(cssSelector, '+')
 
    const parent = vNode.parent
 
    if (!parent || !hasCssSelector(selector, vNode))
      return false
 
    const children = parent.children
 
    if (!children) return false
 
    const index = children.indexOf(vNode)
 
    if (index === 0 || !hasCssSelector(siblingSelector, children[index - 1]))
      return false
 
    return true
  } else Iif (cssSelector.indexOf(' ~ ') > -1) {
    const [ siblingSelector, selector ] = splitByLastIndex(cssSelector, '~')
 
    const parent = vNode.parent
 
    if (!parent || !hasCssSelector(selector, vNode))
      return false
 
    const children = parent.children
 
    if (!children) return false
 
    const index = children.indexOf(vNode)
 
    if (index === 0)
      return false
 
    for (let i = 0; i < index; ++i)
      if (hasCssSelector(siblingSelector, children[i]))
        return true
 
    return false
  } else Iif (cssSelector.indexOf(' ') > -1) {
    const cssSelectors: Array<string> =
      cssSelector.split(' ').filter(Boolean).map((str) => str.trim())
 
    let i = cssSelectors.length - 1
 
    if (!hasCssSelector(cssSelectors[i], vNode))
      return false
 
    while (--i >= 0) {
      const parentMatches =
        traverseParentVNodes((parent) => hasCssSelector(cssSelectors[i], parent), vNode)
 
      if (!parentMatches)
        return false
    }
 
    return true
  }
 
  return matchesSelector(cssSelector, vNode)
}
 
function splitByLastIndex(cssSelector: string, token: string): [string, string] {
  const index = cssSelector.lastIndexOf(token)
 
  return [
    cssSelector.substring(0, index).trim(),
    cssSelector.substring(index + 1).trim(),
  ]
}
 
function traverseParentVNodes(
  predicate: (vNode: VNode) => boolean,
  vNode: VNode): boolean
{
  const parent = vNode.parent
 
  if (!parent) return false
 
  if (predicate(parent))
    return true
 
  return traverseParentVNodes(predicate, parent)
}