Source: controls/Mouse.ts

// @ts-nocheck
import { PressState } from "./PressState";

const PRIMARY_MOUSE_WHICH = 1;

/**
 * @typedef {Object} MouseConfig
 * @property {boolean} [preventContextMenu=false] - Prevent the context menu on the target, so the
 *                                                  secondary button can be used in the game
 */

/**
 * Mouse and touch input handling. Buttons are identified by `Mouse.Button`; touches count as the
 * primary button. A button released outside the target, or while the window loses focus, is
 * released too.
 * @class Mouse
 */
export class Mouse extends PressState {
  /**
   * @param {EventTarget} [target=window] - Target of the mouse and touch events
   * @param {MouseConfig} [config={}]
   */
  constructor(target, config = {}) {
    super();

    this.position = { x: 0, y: 0 };
    this.isOver = false;

    this._target = target ?? window;
    this._preventContextMenu = config.preventContextMenu ?? false;

    this._onMouseDown = this._onMouseDown.bind(this);
    this._onMouseUp = this._onMouseUp.bind(this);
    this._onMouseMove = this._onMouseMove.bind(this);
    this._onMouseLeave = this._onMouseLeave.bind(this);
    this._onContextMenu = this._onContextMenu.bind(this);
    this._onBlur = this.releaseAll.bind(this);

    this._target.addEventListener("mousedown", this._onMouseDown);
    this._target.addEventListener("mouseup", this._onMouseUp);
    this._target.addEventListener("mousemove", this._onMouseMove);
    this._target.addEventListener("mouseleave", this._onMouseLeave);
    this._target.addEventListener("contextmenu", this._onContextMenu);
    this._target.addEventListener("touchstart", this._onMouseDown);
    this._target.addEventListener("touchend", this._onMouseUp);
    this._target.addEventListener("touchmove", this._onMouseMove);
    if (this._target !== globalThis) {
      globalThis.addEventListener?.("mouseup", this._onMouseUp);
    }
    globalThis.addEventListener?.("blur", this._onBlur);
  }

  destruct() {
    this._target.removeEventListener("mousedown", this._onMouseDown);
    this._target.removeEventListener("mouseup", this._onMouseUp);
    this._target.removeEventListener("mousemove", this._onMouseMove);
    this._target.removeEventListener("mouseleave", this._onMouseLeave);
    this._target.removeEventListener("contextmenu", this._onContextMenu);
    this._target.removeEventListener("touchstart", this._onMouseDown);
    this._target.removeEventListener("touchend", this._onMouseUp);
    this._target.removeEventListener("touchmove", this._onMouseMove);
    if (this._target !== globalThis) {
      globalThis.removeEventListener?.("mouseup", this._onMouseUp);
    }
    globalThis.removeEventListener?.("blur", this._onBlur);
  }

  _onMouseDown(event) {
    this._onMouseMove(event);
    this.$setDownState(this._getButton(event));
  }

  _onMouseUp(event) {
    this.$setUpState(this._getButton(event));
  }

  _onMouseMove(event) {
    const position = event.type.startsWith("touch") ? event.touches?.[0] : event;

    if (!position) {
      return;
    }

    this.isOver = true;
    this.position.x = position.clientX;
    this.position.y = position.clientY;
  }

  _onMouseLeave() {
    this.isOver = false;
  }

  _onContextMenu(event) {
    if (this._preventContextMenu) {
      event.preventDefault();
    }
  }

  _getButton(event) {
    if (event.type.startsWith("touch")) {
      return Mouse.Button.PRIMARY;
    }
    return event.button ?? (event.which === PRIMARY_MOUSE_WHICH ? Mouse.Button.PRIMARY : event.which - 1);
  }
}

/**
 * Mouse button ids
 * @member
 * @property {number} PRIMARY
 * @property {number} MIDDLE
 * @property {number} SECONDARY
 */
Mouse.Button = {
  PRIMARY: 0,
  MIDDLE: 1,
  SECONDARY: 2,
};