diff --git a/src/components/cursor-controller.js b/src/components/cursor-controller.js
index be27631195188130eabe5ccd9829d00b3abd7e91..0947f60406f4f3d651b9134939c115d142a61013 100644
--- a/src/components/cursor-controller.js
+++ b/src/components/cursor-controller.js
@@ -11,7 +11,10 @@ AFRAME.registerComponent("cursor-controller", {
     maxDistance: { default: 3 },
     minDistance: { default: 0 },
     cursorColorHovered: { default: "#2F80ED" },
-    cursorColorUnhovered: { default: "#FFFFFF" }
+    cursorColorUnhovered: { default: "#FFFFFF" },
+    rayObject: { type: "selector" },
+    useMousePos: { default: true },
+    drawLine: { default: false }
   },
 
   init: function() {
@@ -22,7 +25,6 @@ AFRAME.registerComponent("cursor-controller", {
     this.currentDistance = this.data.maxDistance;
     this.currentDistanceMod = 0;
     this.mousePos = new THREE.Vector2();
-    this.useMousePos = true;
     this.wasCursorHovered = false;
     this.origin = new THREE.Vector3();
     this.direction = new THREE.Vector3();
@@ -35,6 +37,8 @@ AFRAME.registerComponent("cursor-controller", {
     this.moveCursor = this.moveCursor.bind(this);
     this.endInteraction = this.endInteraction.bind(this);
     this.changeDistanceMod = this.changeDistanceMod.bind(this);
+    this.setRaycasterWithMousePos = this.setRaycasterWithMousePos.bind(this);
+    this.updateDistanceAndTargetType = this.updateDistanceAndTargetType.bind(this);
 
     this._handleCursorLoaded = this._handleCursorLoaded.bind(this);
     this.data.cursor.addEventListener("loaded", this._handleCursorLoaded);
@@ -49,65 +53,89 @@ AFRAME.registerComponent("cursor-controller", {
     this.setCursorVisibility(false);
   },
 
-  tick: function() {
-    if (!this.enabled) {
-      return;
+  update: function() {
+    if (this.data.rayObject) {
+      this.rayObject = this.data.rayObject.object3D;
     }
+  },
 
-    if (this.useMousePos) {
-      const camera = this.data.camera.components.camera.camera;
-      const raycaster = this.el.components.raycaster.raycaster;
-      raycaster.setFromCamera(this.mousePos, camera);
-      this.origin.copy(raycaster.ray.origin);
-      this.direction.copy(raycaster.ray.direction);
-    } else {
-      this.rayObject.getWorldPosition(this.origin);
-      this.rayObject.getWorldDirection(this.direction);
-    }
-    this.el.setAttribute("raycaster", { origin: this.origin, direction: this.direction });
-
-    const isGrabbing = this.data.cursor.components["super-hands"].state.has("grab-start");
-    if (isGrabbing) {
-      const distance = Math.min(
-        this.data.maxDistance,
-        Math.max(this.data.minDistance, this.currentDistance - this.currentDistanceMod)
-      );
-      this.direction.multiplyScalar(distance);
-      this.data.cursor.object3D.position.addVectors(this.origin, this.direction);
-    } else {
-      this.currentDistanceMod = 0;
-      let intersection = null;
-      const intersections = this.el.components.raycaster.intersections;
-      if (intersections.length > 0 && intersections[0].distance <= this.data.maxDistance) {
-        intersection = intersections[0];
-        this.data.cursor.object3D.position.copy(intersection.point);
-        this.currentDistance = intersections[0].distance;
+  tick: (() => {
+    const rayObjectRotation = new THREE.Quaternion();
+
+    return function() {
+      if (!this.enabled) {
+        return;
+      }
+
+      if (this.data.useMousePos) {
+        this.setRaycasterWithMousePos();
       } else {
-        this.currentDistance = this.data.maxDistance;
-        this.direction.multiplyScalar(this.currentDistance);
+        //this.rayObject.updateMatrixWorld();
+        rayObjectRotation.setFromRotationMatrix(this.rayObject.matrixWorld);
+        this.direction
+          .set(0, 0, 1)
+          .applyQuaternion(rayObjectRotation)
+          .normalize();
+        this.origin.setFromMatrixPosition(this.rayObject.matrixWorld);
+      }
+      this.el.setAttribute("raycaster", { origin: this.origin, direction: this.direction });
+
+      const isGrabbing = this.data.cursor.components["super-hands"].state.has("grab-start");
+      if (isGrabbing) {
+        const distance = Math.min(
+          this.data.maxDistance,
+          Math.max(this.data.minDistance, this.currentDistance - this.currentDistanceMod)
+        );
+        this.direction.multiplyScalar(distance);
         this.data.cursor.object3D.position.addVectors(this.origin, this.direction);
+      } else {
+        this.currentDistanceMod = 0;
+        this.updateDistanceAndTargetType();
+
+        const isTarget = this._isTargetOfType(TARGET_TYPE_INTERACTABLE_OR_UI);
+        if (isTarget && !this.wasCursorHovered) {
+          this.wasCursorHovered = true;
+          this.data.cursor.setAttribute("material", { color: this.data.cursorColorHovered });
+        } else if (!isTarget && this.wasCursorHovered) {
+          this.wasCursorHovered = false;
+          this.data.cursor.setAttribute("material", { color: this.data.cursorColorUnhovered });
+        }
       }
 
-      if (!intersection) {
-        this.currentTargetType = TARGET_TYPE_NONE;
-      } else if (intersection.object.el.matches(".interactable, .interactable *")) {
-        this.currentTargetType = TARGET_TYPE_INTERACTABLE;
-      } else if (intersection.object.el.matches(".ui, .ui *")) {
-        this.currentTargetType = TARGET_TYPE_UI;
+      if (this.data.drawLine) {
+        this.el.setAttribute("line", { start: this.origin.clone(), end: this.data.cursor.object3D.position.clone() });
       }
+    };
+  })(),
 
-      const isTarget = this._isTargetOfType(TARGET_TYPE_INTERACTABLE_OR_UI);
-      if (isTarget && !this.wasCursorHovered) {
-        this.wasCursorHovered = true;
-        this.data.cursor.setAttribute("material", { color: this.data.cursorColorHovered });
-      } else if (!isTarget && this.wasCursorHovered) {
-        this.wasCursorHovered = false;
-        this.data.cursor.setAttribute("material", { color: this.data.cursorColorUnhovered });
-      }
+  setRaycasterWithMousePos() {
+    const camera = this.data.camera.components.camera.camera;
+    const raycaster = this.el.components.raycaster.raycaster;
+    raycaster.setFromCamera(this.mousePos, camera);
+    this.origin.copy(raycaster.ray.origin);
+    this.direction.copy(raycaster.ray.direction);
+    this.el.setAttribute("raycaster", { origin: raycaster.ray.origin, direction: raycaster.ray.direction });
+  },
+
+  updateDistanceAndTargetType() {
+    let intersection = null;
+    const intersections = this.el.components.raycaster.intersections;
+    if (intersections.length > 0 && intersections[0].distance <= this.data.maxDistance) {
+      intersection = intersections[0];
+      this.data.cursor.object3D.position.copy(intersection.point);
+      this.currentDistance = intersections[0].distance;
+    } else {
+      this.currentDistance = this.data.maxDistance;
+      this.direction.multiplyScalar(this.currentDistance);
+      this.data.cursor.object3D.position.addVectors(this.origin, this.direction);
     }
 
-    if (this.drawLine) {
-      this.el.setAttribute("line", { start: this.origin.clone(), end: this.data.cursor.object3D.position.clone() });
+    if (!intersection) {
+      this.currentTargetType = TARGET_TYPE_NONE;
+    } else if (intersection.object.el.matches(".interactable, .interactable *")) {
+      this.currentTargetType = TARGET_TYPE_INTERACTABLE;
+    } else if (intersection.object.el.matches(".ui, .ui *")) {
+      this.currentTargetType = TARGET_TYPE_UI;
     }
   },
 
@@ -117,32 +145,14 @@ AFRAME.registerComponent("cursor-controller", {
 
   setCursorVisibility(visible) {
     this.data.cursor.setAttribute("visible", visible);
-    this.el.setAttribute("line", { visible: visible && this.drawLine });
+    this.el.setAttribute("line", { visible: visible && this.data.drawLine });
   },
 
   forceCursorUpdate: function() {
-    // Update the ray and cursor positions
-    const raycasterComp = this.el.components.raycaster;
-    const raycaster = raycasterComp.raycaster;
-    const camera = this.data.camera.components.camera.camera;
-    const cursor = this.data.cursor;
-    raycaster.setFromCamera(this.mousePos, camera);
-    this.el.setAttribute("raycaster", { origin: raycaster.ray.origin, direction: raycaster.ray.direction });
-    raycasterComp.checkIntersections();
-    const intersections = raycasterComp.intersections;
-    if (intersections.length === 0 || intersections[0].distance > this.data.maxDistance) {
-      this.currentTargetType = TARGET_TYPE_NONE;
-      return;
-    }
-    const intersection = intersections[0];
-    if (intersection.object.el.matches(".interactable, .interactable *")) {
-      this.currentTargetType = TARGET_TYPE_INTERACTABLE;
-    } else if (intersection.object.el.matches(".ui, .ui *")) {
-      this.currentTargetType = TARGET_TYPE_UI;
-    }
-    cursor.object3D.position.copy(intersection.point);
-    // Cursor position must be synced to physics before constraint is created
-    cursor.components["static-body"].syncToPhysics();
+    this.setRaycasterWithMousePos();
+    this.el.components.raycaster.checkIntersections();
+    this.updateDistanceAndTargetType();
+    this.data.cursor.components["static-body"].syncToPhysics();
   },
 
   startInteraction: function() {
@@ -164,10 +174,8 @@ AFRAME.registerComponent("cursor-controller", {
   changeDistanceMod: function(delta) {
     const { minDistance, maxDistance } = this.data;
     const targetDistanceMod = this.currentDistanceMod + delta;
-    if (this.currentDistance - targetDistanceMod > maxDistance) {
-      return;
-    }
-    if (this.currentDistance - targetDistanceMod < minDistance) {
+    const moddedDistance = this.currentDistance - targetDistanceMod;
+    if (moddedDistance > maxDistance || moddedDistance < minDistance) {
       return;
     }
     this.currentDistanceMod = targetDistanceMod;
diff --git a/src/components/input-configurator.js b/src/components/input-configurator.js
index 4e7c2cfac7445f4a8c8fbdca6afc5a961d912185..b761deefe6fecef65714350763c05e0b6c8794d4 100644
--- a/src/components/input-configurator.js
+++ b/src/components/input-configurator.js
@@ -4,18 +4,28 @@ import GearVRMouseEventsHandler from "../utils/gearvr-mouse-events-handler.js";
 import ActionEventHandler from "../utils/action-event-handler.js";
 
 AFRAME.registerComponent("input-configurator", {
+  schema: {
+    cursorController: { type: "selector" },
+    gazeTeleporter: { type: "selector" },
+    camera: { type: "selector" },
+    playerRig: { type: "selector" },
+    leftController: { type: "selector" },
+    rightController: { type: "selector" },
+    leftControllerRayObject: { type: "string" },
+    rightControllerRayObject: { type: "string" },
+    gazeCursorRayObject: { type: "string" }
+  },
+
   init() {
     this.inVR = this.el.sceneEl.is("vr-mode");
     this.isMobile = AFRAME.utils.device.isMobile();
     this.eventHandlers = [];
-    this.controller = null;
     this.controllerQueue = [];
     this.hasPointingDevice = false;
-    this.gazeCursorRayObject = document.querySelector("#player-camera-reverse-z");
-    this.cursor = document.querySelector("#cursor-controller").components["cursor-controller"];
-    this.gazeTeleporter = document.querySelector("#gaze-teleport").components["teleport-controls"];
-    this.cameraController = document.querySelector("#player-camera").components["pitch-yaw-rotator"];
-    this.playerRig = document.querySelector("#player-rig");
+    this.cursor = this.data.cursorController.components["cursor-controller"];
+    this.gazeTeleporter = this.data.gazeTeleporter.components["teleport-controls"];
+    this.cameraController = this.data.camera.components["pitch-yaw-rotator"];
+    this.playerRig = this.data.playerRig;
     this.handedness = "right";
 
     this.onEnterVR = this.onEnterVR.bind(this);
@@ -26,21 +36,21 @@ AFRAME.registerComponent("input-configurator", {
     this.handleControllerConnected = this.handleControllerConnected.bind(this);
     this.handleControllerDisconnected = this.handleControllerDisconnected.bind(this);
 
-    this.el.sceneEl.addEventListener("enter-vr", this.onEnterVR);
-    this.el.sceneEl.addEventListener("exit-vr", this.onExitVR);
-
-    this.tearDown();
     this.configureInput();
   },
 
   play() {
     this.el.sceneEl.addEventListener("controllerconnected", this.handleControllerConnected);
     this.el.sceneEl.addEventListener("controllerdisconnected", this.handleControllerDisconnected);
+    this.el.sceneEl.addEventListener("enter-vr", this.onEnterVR);
+    this.el.sceneEl.addEventListener("exit-vr", this.onExitVR);
   },
 
   pause() {
     this.el.sceneEl.removeEventListener("controllerconnected", this.handleControllerConnected);
     this.el.sceneEl.removeEventListener("controllerdisconnected", this.handleControllerDisconnected);
+    this.el.sceneEl.removeEventListener("enter-vr", this.onEnterVR);
+    this.el.sceneEl.removeEventListener("exit-vr", this.onExitVR);
   },
 
   onEnterVR() {
@@ -65,7 +75,6 @@ AFRAME.registerComponent("input-configurator", {
       this.lookOnMobile.el.removeComponent("look-on-mobile");
       this.lookOnMobile = null;
     }
-    this.cameraController.pause();
     this.cursorRequiresManagement = false;
   },
 
@@ -73,19 +82,21 @@ AFRAME.registerComponent("input-configurator", {
     const onAdded = e => {
       if (e.detail.name !== "look-on-mobile") return;
       this.lookOnMobile = this.el.sceneEl.components["look-on-mobile"];
-      this.lookOnMobile.registerCameraController(this.cameraController);
     };
     this.el.sceneEl.addEventListener("componentinitialized", onAdded);
-    this.el.sceneEl.setAttribute("look-on-mobile", "");
+    // This adds look-on-mobile to the scene
+    this.el.sceneEl.setAttribute("look-on-mobile", "camera", this.data.camera);
   },
 
   configureInput() {
+    this.actionEventHandler = new ActionEventHandler(this.el.sceneEl, this.cursor);
+    this.eventHandlers.push(this.actionEventHandler);
+
+    this.cursor.el.setAttribute("cursor-controller", "useMousePos", !this.inVR);
+
     if (this.inVR) {
-      this.cursor.useMousePos = false;
+      this.cameraController.pause();
       this.cursorRequiresManagement = true;
-      this.hovered = false;
-      this.actionEventHandler = new ActionEventHandler(this.el.sceneEl, this.cursor);
-      this.eventHandlers.push(this.actionEventHandler);
       this.cursor.el.setAttribute("cursor-controller", "minDistance", 0);
       if (this.isMobile) {
         this.eventHandlers.push(new GearVRMouseEventsHandler(this.cursor, this.gazeTeleporter));
@@ -94,7 +105,6 @@ AFRAME.registerComponent("input-configurator", {
       }
     } else {
       this.cameraController.play();
-      this.cursor.useMousePos = true;
       if (this.isMobile) {
         this.eventHandlers.push(new TouchEventsHandler(this.cursor, this.cameraController, this.cursor.el));
         this.addLookOnMobile();
@@ -106,18 +116,8 @@ AFRAME.registerComponent("input-configurator", {
   },
 
   tick() {
-    if (!this.cursorRequiresManagement) return;
-
-    if (this.physicalHand) {
-      const state = this.physicalHand.components["super-hands"].state;
-      if (!this.hovered && state.has("hover-start") && !this.actionEventHandler.isCursorInteracting) {
-        this.cursor.disable();
-        this.hovered = true;
-      } else if (this.hovered === true && !state.has("hover-start") && !state.has("grab-start")) {
-        this.cursor.enable();
-        this.cursor.setCursorVisibility(!this.actionEventHandler.isTeleporting);
-        this.hovered = false;
-      }
+    if (this.cursorRequiresManagement && this.controller) {
+      this.actionEventHandler.manageCursorEnabled();
     }
   },
 
@@ -148,7 +148,7 @@ AFRAME.registerComponent("input-configurator", {
 
   updateController: function() {
     this.hasPointingDevice = this.controllerQueue.length > 0 && this.inVR;
-    this.cursor.drawLine = this.hasPointingDevice;
+    this.cursor.el.setAttribute("cursor-controller", "drawLine", this.hasPointingDevice);
 
     this.cursor.setCursorVisibility(true);
 
@@ -156,15 +156,16 @@ AFRAME.registerComponent("input-configurator", {
       const controllerData = this.controllerQueue[0];
       const hand = controllerData.handedness;
       this.controller = controllerData.controller;
-      this.physicalHand = this.playerRig.querySelector(`#player-${hand}-controller`);
-      this.cursor.rayObject = this.controller.querySelector(`#player-${hand}-controller-reverse-z`).object3D;
+      this.cursor.el.setAttribute("cursor-controller", {
+        rayObject: this.hand === "left" ? this.data.leftControllerRayObject : this.data.rightControllerRayObject
+      });
     } else {
       this.controller = null;
-      this.cursor.rayObject = this.gazeCursorRayObject.object3D;
+      this.cursor.el.setAttribute("cursor-controller", { rayObject: this.data.gazeCursorRayObject });
     }
 
     if (this.actionEventHandler) {
-      this.actionEventHandler.setCursorController(this.controller);
+      this.actionEventHandler.setHandThatAlsoDrivesCursor(this.controller);
     }
   }
 });
diff --git a/src/components/look-on-mobile.js b/src/components/look-on-mobile.js
index ff725f50749770e40f72122f021db1a91eeb923d..768df1acdf3073ba65192abc0e5250d9b8e58769 100644
--- a/src/components/look-on-mobile.js
+++ b/src/components/look-on-mobile.js
@@ -29,7 +29,8 @@ const average = a => {
 AFRAME.registerComponent("look-on-mobile", {
   schema: {
     horizontalLookSpeedRatio: { default: 0.4 }, // motion applied to camera / motion of polyfill object
-    verticalLookSpeedRatio: { default: 0.4 } // motion applied to camera / motion of polyfill object
+    verticalLookSpeedRatio: { default: 0.4 }, // motion applied to camera / motion of polyfill object
+    camera: { type: "selector" }
   },
 
   init() {
@@ -54,12 +55,12 @@ AFRAME.registerComponent("look-on-mobile", {
     this.polyfillObject = null;
   },
 
-  onRotateX(e) {
-    this.pendingLookX = e.detail.value;
+  update() {
+    this.cameraController = this.data.camera.components["pitch-yaw-rotator"];
   },
 
-  registerCameraController(cameraController) {
-    this.cameraController = cameraController;
+  onRotateX(e) {
+    this.pendingLookX = e.detail.value;
   },
 
   tick() {
diff --git a/src/components/pitch-yaw-rotator b/src/components/pitch-yaw-rotator.js
similarity index 56%
rename from src/components/pitch-yaw-rotator
rename to src/components/pitch-yaw-rotator.js
index cc62dd1c5a2e8f8e77a5c52357fd7dda2bdb267b..87f4e646959ead44cec4b9223a52535081244240 100644
--- a/src/components/pitch-yaw-rotator
+++ b/src/components/pitch-yaw-rotator.js
@@ -1,3 +1,4 @@
+const degToRad = THREE.Math.degToRad;
 AFRAME.registerComponent("pitch-yaw-rotator", {
   schema: {
     minPitch: { default: -50 },
@@ -20,6 +21,10 @@ AFRAME.registerComponent("pitch-yaw-rotator", {
   tick() {
     this.rotation.x = this.pitch;
     this.rotation.y = this.yaw;
-    this.el.setAttribute("rotation", this.rotation);
+
+    // Update rotation of object3D the same way the rotation component of aframe does,
+    // skipping the work that would be done if we used this.el.setAttribute("rotation", this.rotation);
+    this.el.object3D.rotation.set(degToRad(this.rotation.x), degToRad(this.rotation.y), degToRad(this.rotation.z));
+    this.el.object3D.rotation.order = "YXZ";
   }
 });
diff --git a/src/hub.html b/src/hub.html
index 36708f68167224897d1e86d82be8596ae553ae40..24f7b5e8009fb064de253eaa0e9b7a71126b3674 100644
--- a/src/hub.html
+++ b/src/hub.html
@@ -42,7 +42,16 @@
         personal-space-bubble="debug: false;"
         vr-mode-ui="enabled: false"
         pinch-to-move
-        input-configurator
+        input-configurator="
+                  gazeCursorRayObject: #player-camera-reverse-z;
+                  cursorController: #cursor-controller;
+                  gazeTeleporter: #gaze-teleport;
+                  camera: #player-camera;
+                  playerRig: #player-rig;
+                  leftController: #player-left-controller;
+                  leftControllerRayObject: #player-left-controller-reverse-z;
+                  rightController: #player-right-controller;
+                  rightControllerRayObject: #player-right-controller-reverse-z;"
     >
 
         <a-assets>
@@ -203,10 +212,7 @@
             id="cursor-controller"
             cursor-controller="
                 cursor: #cursor;
-                camera: #player-camera;
-                playerRig: #player-rig;
-                physicalHandSelector: #player-right-controller;
-                gazeTeleportControls: #gaze-teleport;"
+                camera: #player-camera; "
             raycaster="objects: .collidable, .interactable, .ui; far: 3;"
             line="visible: false; color: white; opacity: 0.2;"
         ></a-entity>
diff --git a/src/utils/action-event-handler.js b/src/utils/action-event-handler.js
index bd2ed13a1bd9a66f4d3b86312c97e76377e857a1..1884a7313bf88bc345eff106856c270403d2a74f 100644
--- a/src/utils/action-event-handler.js
+++ b/src/utils/action-event-handler.js
@@ -4,7 +4,8 @@ export default class ActionEventHandler {
     this.cursor = cursor;
     this.isCursorInteracting = false;
     this.isTeleporting = false;
-    this.cursorController = null;
+    this.handThatAlsoDrivesCursor = null;
+    this.hovered = false;
 
     this.addEventListeners = this.addEventListeners.bind(this);
     this.tearDown = this.tearDown.bind(this);
@@ -15,6 +16,7 @@ export default class ActionEventHandler {
     this.onCardboardButtonDown = this.onCardboardButtonDown.bind(this);
     this.onCardboardButtonUp = this.onCardboardButtonUp.bind(this);
     this.onMoveDuck = this.onMoveDuck.bind(this);
+    this.manageCursorEnabled = this.manageCursorEnabled.bind(this);
     this.addEventListeners();
   }
 
@@ -42,12 +44,12 @@ export default class ActionEventHandler {
     this.cursor.changeDistanceMod(-e.detail.axis[1] / 8);
   }
 
-  setCursorController(cursorController) {
-    this.cursorController = cursorController;
+  setHandThatAlsoDrivesCursor(handThatAlsoDrivesCursor) {
+    this.handThatAlsoDrivesCursor = handThatAlsoDrivesCursor;
   }
 
   onGrab(e) {
-    if (this.cursorController && this.cursorController === e.target) {
+    if (this.handThatAlsoDrivesCursor && this.handThatAlsoDrivesCursor === e.target) {
       if (this.isCursorInteracting) {
         return;
       } else if (e.target.components["super-hands"].state.has("hover-start")) {
@@ -64,7 +66,7 @@ export default class ActionEventHandler {
   }
 
   onRelease(e) {
-    if (this.isCursorInteracting && this.cursorController && this.cursorController === e.target) {
+    if (this.isCursorInteracting && this.handThatAlsoDrivesCursor && this.handThatAlsoDrivesCursor === e.target) {
       this.isCursorInteracting = false;
       this.cursor.endInteraction();
     } else {
@@ -73,7 +75,7 @@ export default class ActionEventHandler {
   }
 
   onPrimaryDown(e) {
-    if (this.cursorController && this.cursorController === e.target) {
+    if (this.handThatAlsoDrivesCursor && this.handThatAlsoDrivesCursor === e.target) {
       if (this.isCursorInteracting) {
         return;
       } else if (e.target.components["super-hands"].state.has("hover-start")) {
@@ -92,7 +94,7 @@ export default class ActionEventHandler {
   }
 
   onPrimaryUp(e) {
-    const isCursorHand = this.cursorController && this.cursorController === e.target;
+    const isCursorHand = this.handThatAlsoDrivesCursor && this.handThatAlsoDrivesCursor === e.target;
     if (this.isCursorInteracting && isCursorHand) {
       this.isCursorInteracting = false;
       this.cursor.endInteraction();
@@ -141,4 +143,19 @@ export default class ActionEventHandler {
     gazeTeleport.emit(button + "up");
     this.isTeleporting = false;
   }
+
+  manageCursorEnabled() {
+    const handState = this.handThatAlsoDrivesCursor.components["super-hands"].state;
+    const handHoveredThisFrame = !this.hovered && handState.has("hover-start") && !this.isCursorInteracting;
+    const handStoppedHoveringThisFrame =
+      this.hovered === true && !handState.has("hover-start") && !handState.has("grab-start");
+    if (handHoveredThisFrame) {
+      this.hovered = true;
+      this.cursor.disable();
+    } else if (handStoppedHoveringThisFrame) {
+      this.hovered = false;
+      this.cursor.enable();
+      this.cursor.setCursorVisibility(!this.isTeleporting);
+    }
+  }
 }
diff --git a/src/utils/mouse-events-handler.js b/src/utils/mouse-events-handler.js
index 9c6b71af828e2e09300db7decf5f9e3d9de0f4bc..b034006a1fafa9756cc94928288622a7b022bb07 100644
--- a/src/utils/mouse-events-handler.js
+++ b/src/utils/mouse-events-handler.js
@@ -28,9 +28,7 @@ export default class MouseEventsHandler {
     document.removeEventListener("mousemove", this.onMouseMove);
     document.removeEventListener("mouseup", this.onMouseUp);
     document.removeEventListener("wheel", this.onMouseWheel);
-    document.removeEventListener("contextmenu", e => {
-      e.preventDefault();
-    });
+    document.removeEventListener("contextmenu", this.onContextMenu);
   }
 
   setInverseMouseLook(invert) {
@@ -42,9 +40,11 @@ export default class MouseEventsHandler {
     document.addEventListener("mousemove", this.onMouseMove);
     document.addEventListener("mouseup", this.onMouseUp);
     document.addEventListener("wheel", this.onMouseWheel);
-    document.addEventListener("contextmenu", e => {
-      e.preventDefault();
-    });
+    document.addEventListener("contextmenu", this.onContextMenu);
+  }
+
+  onContextMenu(e) {
+    e.preventDefault();
   }
 
   onMouseDown(e) {