mouseMoveEvent = (evt) => {
  // if the mouse is move without dragging an element, just return.
  if (!svgCanvas.getStarted()) { return }
  if (evt.button === 1 || svgCanvas.spaceKey) { return }

  svgCanvas.textActions.init()

  evt.preventDefault()

  const selectedElements = svgCanvas.getSelectedElements()
  const zoom = svgCanvas.getZoom()
  const svgRoot = svgCanvas.getSvgRoot()
  const selected = selectedElements[0]

  let i
  let xya
  let cx
  let cy
  let dx
  let dy
  let len
  let angle
  let box

  const pt = transformPoint(evt.clientX, evt.clientY, svgCanvas.getrootSctm())
  const mouseX = pt.x * zoom
  const mouseY = pt.y * zoom
  const shape = getElement(svgCanvas.getId())

  let realX = mouseX / zoom
  let x = realX
  let realY = mouseY / zoom
  let y = realY

  if (svgCanvas.getCurConfig().gridSnapping) {
    x = snapToGrid(x)
    y = snapToGrid(y)
  }

  let tlist
  switch (svgCanvas.getCurrentMode()) {
    case 'select': {
      // we temporarily use a translate on the element(s) being dragged
      // this transform is removed upon mousing up and the element is
      // relocated to the new location
      if (selected) {
        dx = x - svgCanvas.getStartX()
        dy = y - svgCanvas.getStartY()
        if (svgCanvas.getCurConfig().gridSnapping) {
          dx = snapToGrid(dx)
          dy = snapToGrid(dy)
        }

        // Enable moving selection only if mouse has been moved at least 4 px in any direction
        // This prevents objects from being accidentally moved when (initially) selected
        const deltaThreshold = 4
        const deltaThresholdReached = Math.abs(dx) > deltaThreshold || Math.abs(dy) > deltaThreshold
        moveSelectionThresholdReached = moveSelectionThresholdReached || deltaThresholdReached

        if (moveSelectionThresholdReached) {
          selectedElements.forEach((el) => {
            if (el) {
              updateTransformList(svgRoot, el, dx, dy)
              // update our internal bbox that we're tracking while dragging
              svgCanvas.selectorManager.requestSelector(el).resize()
            }
          })
          svgCanvas.call('transition', selectedElements)
        }
      }
      break
    }
    case 'multiselect': {
      realX *= zoom
      realY *= zoom
      assignAttributes(svgCanvas.getRubberBox(), {
        x: Math.min(svgCanvas.getRStartX(), realX),
        y: Math.min(svgCanvas.getRStartY(), realY),
        width: Math.abs(realX - svgCanvas.getRStartX()),
        height: Math.abs(realY - svgCanvas.getRStartY())
      }, 100)

      // for each selected:
      // - if newList contains selected, do nothing
      // - if newList doesn't contain selected, remove it from selected
      // - for any newList that was not in selectedElements, add it to selected
      const elemsToRemove = selectedElements.slice(); const elemsToAdd = []
      const newList = svgCanvas.getIntersectionList()

      // For every element in the intersection, add if not present in selectedElements.
      len = newList.length
      for (i = 0; i < len; ++i) {
        const intElem = newList[i]
        // Found an element that was not selected before, so we should add it.
        if (!selectedElements.includes(intElem)) {
          elemsToAdd.push(intElem)
        }
        // Found an element that was already selected, so we shouldn't remove it.
        const foundInd = elemsToRemove.indexOf(intElem)
        if (foundInd !== -1) {
          elemsToRemove.splice(foundInd, 1)
        }
      }

      if (elemsToRemove.length > 0) {
        svgCanvas.removeFromSelection(elemsToRemove)
      }

      if (elemsToAdd.length > 0) {
        svgCanvas.addToSelection(elemsToAdd)
      }

      break
    }
    case 'resize': {
      // we track the resize bounding box and translate/scale the selected element
      // while the mouse is down, when mouse goes up, we use this to recalculate
      // the shape's coordinates
      tlist = getTransformList(selected)
      const hasMatrix = hasMatrixTransform(tlist)
      box = hasMatrix ? svgCanvas.getInitBbox() : getBBox(selected)
      let left = box.x
      let top = box.y
      let { width, height } = box
      dx = (x - svgCanvas.getStartX())
      dy = (y - svgCanvas.getStartY())

      if (svgCanvas.getCurConfig().gridSnapping) {
        dx = snapToGrid(dx)
        dy = snapToGrid(dy)
        height = snapToGrid(height)
        width = snapToGrid(width)
      }

      // if rotated, adjust the dx,dy values
      angle = getRotationAngle(selected)
      if (angle) {
        const r = Math.sqrt(dx * dx + dy * dy)
        const theta = Math.atan2(dy, dx) - angle * Math.PI / 180.0
        dx = r * Math.cos(theta)
        dy = r * Math.sin(theta)
      }

      // if not stretching in y direction, set dy to 0
      // if not stretching in x direction, set dx to 0
      if (!svgCanvas.getCurrentResizeMode().includes('n') && !svgCanvas.getCurrentResizeMode().includes('s')) {
        dy = 0
      }
      if (!svgCanvas.getCurrentResizeMode().includes('e') && !svgCanvas.getCurrentResizeMode().includes('w')) {
        dx = 0
      }

      let // ts = null,
        tx = 0; let ty = 0
      let sy = height ? (height + dy) / height : 1
      let sx = width ? (width + dx) / width : 1
      // if we are dragging on the north side, then adjust the scale factor and ty
      if (svgCanvas.getCurrentResizeMode().includes('n')) {
        sy = height ? (height - dy) / height : 1
        ty = height
      }

      // if we dragging on the east side, then adjust the scale factor and tx
      if (svgCanvas.getCurrentResizeMode().includes('w')) {
        sx = width ? (width - dx) / width : 1
        tx = width
      }

      // update the transform list with translate,scale,translate
      const translateOrigin = svgRoot.createSVGTransform()
      const scale = svgRoot.createSVGTransform()
      const translateBack = svgRoot.createSVGTransform()

      if (svgCanvas.getCurConfig().gridSnapping) {
        left = snapToGrid(left)
        tx = snapToGrid(tx)
        top = snapToGrid(top)
        ty = snapToGrid(ty)
      }

      translateOrigin.setTranslate(-(left + tx), -(top + ty))
      // For images, we maintain aspect ratio by default and relax when shift pressed
      const maintainAspectRatio = (selected.tagName !== 'image' && evt.shiftKey) || (selected.tagName === 'image' && !evt.shiftKey)
      if (maintainAspectRatio) {
        if (sx === 1) {
          sx = sy
        } else { sy = sx }
      }
      scale.setScale(sx, sy)

      translateBack.setTranslate(left + tx, top + ty)
      if (hasMatrix) {
        const diff = angle ? 1 : 0
        tlist.replaceItem(translateOrigin, 2 + diff)
        tlist.replaceItem(scale, 1 + diff)
        tlist.replaceItem(translateBack, Number(diff))
      } else {
        const N = tlist.numberOfItems
        tlist.replaceItem(translateBack, N - 3)
        tlist.replaceItem(scale, N - 2)
        tlist.replaceItem(translateOrigin, N - 1)
      }

      svgCanvas.selectorManager.requestSelector(selected).resize()
      svgCanvas.call('transition', selectedElements)

      break
    }
    case 'zoom': {
      realX *= zoom
      realY *= zoom
      assignAttributes(svgCanvas.getRubberBox(), {
        x: Math.min(svgCanvas.getRStartX() * zoom, realX),
        y: Math.min(svgCanvas.getRStartY() * zoom, realY),
        width: Math.abs(realX - svgCanvas.getRStartX() * zoom),
        height: Math.abs(realY - svgCanvas.getRStartY() * zoom)
      }, 100)
      break
    }
    case 'text': {
      assignAttributes(shape, {
        x,
        y
      }, 1000)
      break
    }
    case 'line': {
      if (svgCanvas.getCurConfig().gridSnapping) {
        x = snapToGrid(x)
        y = snapToGrid(y)
      }

      let x2 = x
      let y2 = y

      if (evt.shiftKey) {
        xya = snapToAngle(svgCanvas.getStartX(), svgCanvas.getStartY(), x2, y2)
        x2 = xya.x
        y2 = xya.y
      }

      shape.setAttribute('x2', x2)
      shape.setAttribute('y2', y2)
      break
    }
    case 'foreignObject': // fall through
    case 'square':
    case 'rect':
    case 'image': {
      // For images, we maintain aspect ratio by default and relax when shift pressed
      const maintainAspectRatio = (svgCanvas.getCurrentMode() === 'square') ||
        (svgCanvas.getCurrentMode() === 'image' && !evt.shiftKey) ||
        (svgCanvas.getCurrentMode() !== 'image' && evt.shiftKey)

      let
        w = Math.abs(x - svgCanvas.getStartX())
      let h = Math.abs(y - svgCanvas.getStartY())
      let newX; let newY
      if (maintainAspectRatio) {
        w = h = Math.max(w, h)
        newX = svgCanvas.getStartX() < x ? svgCanvas.getStartX() : svgCanvas.getStartX() - w
        newY = svgCanvas.getStartY() < y ? svgCanvas.getStartY() : svgCanvas.getStartY() - h
      } else {
        newX = Math.min(svgCanvas.getStartX(), x)
        newY = Math.min(svgCanvas.getStartY(), y)
      }

      if (svgCanvas.getCurConfig().gridSnapping) {
        w = snapToGrid(w)
        h = snapToGrid(h)
        newX = snapToGrid(newX)
        newY = snapToGrid(newY)
      }

      assignAttributes(shape, {
        width: w,
        height: h,
        x: newX,
        y: newY
      }, 1000)

      break
    }
    case 'circle': {
      cx = Number(shape.getAttribute('cx'))
      cy = Number(shape.getAttribute('cy'))
      let rad = Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy))
      if (svgCanvas.getCurConfig().gridSnapping) {
        rad = snapToGrid(rad)
      }
      shape.setAttribute('r', rad)
      break
    }
    case 'ellipse': {
      cx = Number(shape.getAttribute('cx'))
      cy = Number(shape.getAttribute('cy'))
      if (svgCanvas.getCurConfig().gridSnapping) {
        x = snapToGrid(x)
        cx = snapToGrid(cx)
        y = snapToGrid(y)
        cy = snapToGrid(cy)
      }
      shape.setAttribute('rx', Math.abs(x - cx))
      const ry = Math.abs(evt.shiftKey ? (x - cx) : (y - cy))
      shape.setAttribute('ry', ry)
      break
    }
    case 'fhellipse':
    case 'fhrect': {
      svgCanvas.setFreehand('minx', Math.min(realX, svgCanvas.getFreehand('minx')))
      svgCanvas.setFreehand('maxx', Math.max(realX, svgCanvas.getFreehand('maxx')))
      svgCanvas.setFreehand('miny', Math.min(realY, svgCanvas.getFreehand('miny')))
      svgCanvas.setFreehand('maxy', Math.max(realY, svgCanvas.getFreehand('maxy')))
    }
    // Fallthrough
    case 'fhpath': {
      // dAttr += + realX + ',' + realY + ' ';
      // shape.setAttribute('points', dAttr);
      svgCanvas.setEnd('x', realX)
      svgCanvas.setEnd('y', realY)
      if (svgCanvas.getControllPoint2('x') && svgCanvas.getControllPoint2('y')) {
        for (i = 0; i < svgCanvas.getStepCount() - 1; i++) {
          svgCanvas.setParameter(i / svgCanvas.getStepCount())
          svgCanvas.setNextParameter((i + 1) / svgCanvas.getStepCount())
          svgCanvas.setbSpline(getBsplinePoint(svgCanvas.getNextParameter()))
          svgCanvas.setNextPos({ x: svgCanvas.getbSpline('x'), y: svgCanvas.getbSpline('y') })
          svgCanvas.setbSpline(getBsplinePoint(svgCanvas.getParameter()))
          svgCanvas.setSumDistance(
            svgCanvas.getSumDistance() + Math.sqrt((svgCanvas.getNextPos('x') -
              svgCanvas.getbSpline('x')) * (svgCanvas.getNextPos('x') -
                svgCanvas.getbSpline('x')) + (svgCanvas.getNextPos('y') -
                  svgCanvas.getbSpline('y')) * (svgCanvas.getNextPos('y') - svgCanvas.getbSpline('y')))
          )
          if (svgCanvas.getSumDistance() > svgCanvas.getThreSholdDist()) {
            svgCanvas.setSumDistance(svgCanvas.getSumDistance() - svgCanvas.getThreSholdDist())

            // Faster than completely re-writing the points attribute.
            const point = svgCanvas.getSvgContent().createSVGPoint()
            point.x = svgCanvas.getbSpline('x')
            point.y = svgCanvas.getbSpline('y')
            shape.points.appendItem(point)
          }
        }
      }
      svgCanvas.setControllPoint2('x', svgCanvas.getControllPoint1('x'))
      svgCanvas.setControllPoint2('y', svgCanvas.getControllPoint1('y'))
      svgCanvas.setControllPoint1('x', svgCanvas.getStart('x'))
      svgCanvas.setControllPoint1('y', svgCanvas.getStart('y'))
      svgCanvas.setStart({ x: svgCanvas.getEnd('x'), y: svgCanvas.getEnd('y') })
      break
      // update path stretch line coordinates
    }
    case 'path': // fall through
    case 'pathedit': {
      x *= zoom
      y *= zoom

      if (svgCanvas.getCurConfig().gridSnapping) {
        x = snapToGrid(x)
        y = snapToGrid(y)
        svgCanvas.setStartX(snapToGrid(svgCanvas.getStartX()))
        svgCanvas.setStartY(snapToGrid(svgCanvas.getStartY()))
      }
      if (evt.shiftKey) {
        const { path } = pathModule
        let x1; let y1
        if (path) {
          x1 = path.dragging ? path.dragging[0] : svgCanvas.getStartX()
          y1 = path.dragging ? path.dragging[1] : svgCanvas.getStartY()
        } else {
          x1 = svgCanvas.getStartX()
          y1 = svgCanvas.getStartY()
        }
        xya = snapToAngle(x1, y1, x, y);
        ({ x, y } = xya)
      }

      if (svgCanvas.getRubberBox()?.getAttribute('display') !== 'none') {
        realX *= zoom
        realY *= zoom
        assignAttributes(svgCanvas.getRubberBox(), {
          x: Math.min(svgCanvas.getRStartX() * zoom, realX),
          y: Math.min(svgCanvas.getRStartY() * zoom, realY),
          width: Math.abs(realX - svgCanvas.getRStartX() * zoom),
          height: Math.abs(realY - svgCanvas.getRStartY() * zoom)
        }, 100)
      }
      svgCanvas.pathActions.mouseMove(x, y)

      break
    }
    case 'textedit': {
      x *= zoom
      y *= zoom
      svgCanvas.textActions.mouseMove(mouseX, mouseY)

      break
    }
    case 'rotate': {
      box = getBBox(selected)
      cx = box.x + box.width / 2
      cy = box.y + box.height / 2
      const m = getMatrix(selected)
      const center = transformPoint(cx, cy, m)
      cx = center.x
      cy = center.y
      angle = ((Math.atan2(cy - y, cx - x) * (180 / Math.PI)) - 90) % 360
      if (svgCanvas.getCurConfig().gridSnapping) {
        angle = snapToGrid(angle)
      }
      if (evt.shiftKey) { // restrict rotations to nice angles (WRS)
        const snap = 45
        angle = Math.round(angle / snap) * snap
      }

      svgCanvas.setRotationAngle(angle < -180 ? (360 + angle) : angle, true)
      svgCanvas.call('transition', selectedElements)
      break
    }
    default:
      // A mode can be defined by an extenstion
      break
  }

  /**
  * The mouse has moved on the canvas area.
  * @event module:svgcanvas.SvgCanvas#event:ext_mouseMove
  * @type {PlainObject}
  * @property {MouseEvent} event The event object
  * @property {Float} mouse_x x coordinate on canvas
  * @property {Float} mouse_y y coordinate on canvas
  * @property {Element} selected Refers to the first selected element
  */
  svgCanvas.runExtensions('mouseMove', /** @type {module:svgcanvas.SvgCanvas#event:ext_mouseMove} */ {
    event: evt,
    mouse_x: mouseX,
    mouse_y: mouseY,
    selected
  })
}
@param {MouseEvent} evt
@fires module:svgcanvas.SvgCanvas#event:transition
@fires module:svgcanvas.SvgCanvas#event:ext_mouseMove
@returns {void}