text: import { IS_NON_DIMENSIONAL } from '../constants'
  import { applyRef } from '../util'
  import options from '../options'
  
  /**
   * A DOM event listener
   * @typedef {(e: Event) => void} EventListner
   */
  
  /**
   * A mapping of event types to event listeners
   * @typedef {Object.<string, EventListener>} EventListenerMap
   */
  
  /**
   * Properties Preact adds to elements it creates
   * @typedef PreactElementExtensions
   * @property {string} [normalizedNodeName] A normalized node name to use in diffing
   * @property {EventListenerMap} [_listeners] A map of event listeners added by components to this DOM node
   * @property {import('../component').Component} [_component] The component that rendered this DOM node
   * @property {function} [_componentConstructor] The constructor of the component that rendered this DOM node
   */
  
  /**
   * A DOM element that has been extended with Preact properties
   * @typedef {Element & ElementCSSInlineStyle & PreactElementExtensions} PreactElement
   */
  
  /**
   * Create an element with the given nodeName.
   * @param {string} nodeName The DOM node to create
   * @param {boolean} [isSvg=false] If `true`, creates an element within the SVG
   *  namespace.
   * @returns {PreactElement} The created DOM node
   */
  export function createNode(nodeName, isSvg) {
    /** @type {PreactElement} */
    let node = isSvg
      ? document.createElementNS('http://www.w3.org/2000/svg', nodeName)
      : document.createElement(nodeName)
    node.normalizedNodeName = nodeName
    return node
  }
  
  /**
   * Remove a child node from its parent if attached.
   * @param {Node} node The node to remove
   */
  export function removeNode(node) {
    let parentNode = node.parentNode
    if (parentNode) parentNode.removeChild(node)
  }
  
  /**
   * Set a named attribute on the given Node, with special behavior for some names
   * and event handlers. If `value` is `null`, the attribute/handler will be
   * removed.
   * @param {PreactElement} node An element to mutate
   * @param {string} name The name/key to set, such as an event or attribute name
   * @param {*} old The last value that was set for this name/node pair
   * @param {*} value An attribute value, such as a function to be used as an
   *  event handler
   * @param {boolean} isSvg Are we currently diffing inside an svg?
   * @private
   */
  export function setAccessor(node, name, old, value, isSvg) {
    if (name === 'className') name = 'class'
  
    if (name === 'key') {
      // ignore
    } else if (name === 'ref') {
      applyRef(old, null)
      applyRef(value, node)
    } else if (name === 'class' && !isSvg) {
      node.className = value || ''
    } else if (name === 'style') {
      if (!value || typeof value === 'string' || typeof old === 'string') {
        node.style.cssText = value || ''
      }
      if (value && typeof value === 'object') {
        if (typeof old !== 'string') {
          for (let i in old) if (!(i in value)) node.style[i] = ''
        }
        for (let i in value) {
          node.style[i] =
            typeof value[i] === 'number' && IS_NON_DIMENSIONAL.test(i) === false
              ? value[i] + 'px'
              : value[i]
        }
      }
    } else if (name === 'dangerouslySetInnerHTML') {
      if (value) node.innerHTML = value.__html || ''
    } else if (name[0] == 'o' && name[1] == 'n') {
      let useCapture = name !== (name = name.replace(/Capture$/, ''))
      name = name.toLowerCase().substring(2)
      if (value) {
        if (!old) node.addEventListener(name, eventProxy, useCapture)
      } else {
        node.removeEventListener(name, eventProxy, useCapture)
      }
      ;(node._listeners || (node._listeners = {}))[name] = value
    } else if (name !== 'list' && name !== 'type' && !isSvg && name in node) {
      // Attempt to set a DOM property to the given value.
      // IE & FF throw for certain property-value combinations.
      try {
        node[name] = value == null ? '' : value
      } catch (e) {}
      if ((value == null || value === false) && name != 'spellcheck')
        node.removeAttribute(name)
    } else {
      let ns = isSvg && name !== (name = name.replace(/^xlink:?/, ''))
      // spellcheck is treated differently than all other boolean values and
      // should not be removed when the value is `false`. See:
      // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attr-spellcheck
      if (value == null || value === false) {
        if (ns)
          node.removeAttributeNS(
            'http://www.w3.org/1999/xlink',
            name.toLowerCase()
          )
        else node.removeAttribute(name)
      } else if (typeof value === 'string') {
        if (ns) {
          node.setAttributeNS(
            'http://www.w3.org/1999/xlink',
            name.toLowerCase(),
            value
          )
        } else {
          node.setAttribute(name, value)
        }
      }
    }
  }
  
  /**
   * Proxy an event to hooked event handlers
   * @param {Event} e The event object from the browser
   * @private
   */
  function eventProxy(e) {
    return this._listeners[e.type]((options.event && options.event(e)) || e)
  }
  