text: import { kebabCase, camelCase, debounce, isNumeric } from '../utils'
  
  export function registerListener(component, el, event, modifiers, expression, extraVars = {}) {
      const options = {
          passive: modifiers.includes('passive'),
      };
  
      if (modifiers.includes('camel')) {
          event = camelCase(event);
      }
  
      if (modifiers.includes('away')) {
          let handler = e => {
              // Don't do anything if the click came from the element or within it.
              if (el.contains(e.target)) return
  
              // Don't do anything if this element isn't currently visible.
              if (el.offsetWidth < 1 && el.offsetHeight < 1) return
  
              // Now that we are sure the element is visible, AND the click
              // is from outside it, let's run the expression.
              runListenerHandler(component, expression, e, extraVars)
  
              if (modifiers.includes('once')) {
                  document.removeEventListener(event, handler, options)
              }
          }
  
          // Listen for this event at the root level.
          document.addEventListener(event, handler, options)
      } else {
          let listenerTarget = modifiers.includes('window')
              ? window : (modifiers.includes('document') ? document : el)
  
          let handler = e => {
              // Remove this global event handler if the element that declared it
              // has been removed. It's now stale.
              if (listenerTarget === window || listenerTarget === document) {
                  if (! document.body.contains(el)) {
                      listenerTarget.removeEventListener(event, handler, options)
                      return
                  }
              }
  
              if (isKeyEvent(event)) {
                  if (isListeningForASpecificKeyThatHasntBeenPressed(e, modifiers)) {
                      return
                  }
              }
  
              if (modifiers.includes('prevent')) e.preventDefault()
              if (modifiers.includes('stop')) e.stopPropagation()
  
              // If the .self modifier isn't present, or if it is present and
              // the target element matches the element we are registering the
              // event on, run the handler
              if (! modifiers.includes('self') || e.target === el) {
                  const returnValue = runListenerHandler(component, expression, e, extraVars)
  
                  if (returnValue === false) {
                      e.preventDefault()
                  } else {
                      if (modifiers.includes('once')) {
                          listenerTarget.removeEventListener(event, handler, options)
                      }
                  }
              }
          }
  
          if (modifiers.includes('debounce')) {
              let nextModifier = modifiers[modifiers.indexOf('debounce')+1] || 'invalid-wait'
              let wait = isNumeric(nextModifier.split('ms')[0]) ? Number(nextModifier.split('ms')[0]) : 250
              handler = debounce(handler, wait, this)
          }
  
          listenerTarget.addEventListener(event, handler, options)
      }
  }
  
  function runListenerHandler(component, expression, e, extraVars) {
      return component.evaluateCommandExpression(e.target, expression, () => {
          return {...extraVars(), '$event': e}
      })
  }
  
  function isKeyEvent(event) {
      return ['keydown', 'keyup'].includes(event)
  }
  
  function isListeningForASpecificKeyThatHasntBeenPressed(e, modifiers) {
      let keyModifiers = modifiers.filter(i => {
          return ! ['window', 'document', 'prevent', 'stop'].includes(i)
      })
  
      if (keyModifiers.includes('debounce')) {
          let debounceIndex = keyModifiers.indexOf('debounce')
          keyModifiers.splice(debounceIndex, isNumeric((keyModifiers[debounceIndex+1] || 'invalid-wait').split('ms')[0]) ? 2 : 1)
      }
  
      // If no modifier is specified, we'll call it a press.
      if (keyModifiers.length === 0) return false
  
      // If one is passed, AND it matches the key pressed, we'll call it a press.
      if (keyModifiers.length === 1 && keyModifiers[0] === keyToModifier(e.key)) return false
  
      // The user is listening for key combinations.
      const systemKeyModifiers = ['ctrl', 'shift', 'alt', 'meta', 'cmd', 'super']
      const selectedSystemKeyModifiers = systemKeyModifiers.filter(modifier => keyModifiers.includes(modifier))
  
      keyModifiers = keyModifiers.filter(i => ! selectedSystemKeyModifiers.includes(i))
  
      if (selectedSystemKeyModifiers.length > 0) {
          const activelyPressedKeyModifiers = selectedSystemKeyModifiers.filter(modifier => {
              // Alias "cmd" and "super" to "meta"
              if (modifier === 'cmd' || modifier === 'super') modifier = 'meta'
  
              return e[`${modifier}Key`]
          })
  
          // If all the modifiers selected are pressed, ...
          if (activelyPressedKeyModifiers.length === selectedSystemKeyModifiers.length) {
              // AND the remaining key is pressed as well. It's a press.
              if (keyModifiers[0] === keyToModifier(e.key)) return false
          }
      }
  
      // We'll call it NOT a valid keypress.
      return true
  }
  
  function keyToModifier(key) {
      switch (key) {
          case '/':
              return 'slash'
          case ' ':
          case 'Spacebar':
              return 'space'
          default:
              return key && kebabCase(key)
      }
  }
  