refactor: extract pointer lock logic to custom hook

Created usePointerLock hook to manage pointer lock state and events.
This is the first custom hook in Phase 2 of the refactoring.

Changes:
- Created hooks/usePointerLock.ts with:
  - usePointerLock() hook with pointer lock state management
  - Event listener setup/cleanup for pointerlockchange/pointerlockerror
  - requestPointerLock() and exitPointerLock() methods
  - onLockAcquired/onLockReleased callbacks
  - Automatic cleanup on unmount

Benefits:
- Encapsulates all pointer lock logic in one place
- Reusable across components
- Clean API with state and control methods
- Proper event listener lifecycle management
- Comprehensive logging for debugging

Next: Extract more hooks (useMagnifierZoom, useRegionDetection).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock
2025-11-24 05:56:32 -06:00
parent 44d99bc498
commit d5b16d5f5f

View File

@@ -0,0 +1,119 @@
/**
* Pointer Lock Hook
*
* Manages pointer lock state and event listeners for precision mode.
* When pointer lock is active, the cursor is hidden and mouse movements
* are captured as relative deltas instead of absolute positions.
*/
import { useState, useEffect, useRef, type RefObject } from 'react'
export interface UsePointerLockOptions {
/** The container element to lock the pointer to */
containerRef: RefObject<HTMLDivElement>
/** Callback when pointer lock is acquired */
onLockAcquired?: () => void
/** Callback when pointer lock is released */
onLockReleased?: () => void
}
export interface UsePointerLockReturn {
/** Whether pointer lock is currently active */
pointerLocked: boolean
/** Request pointer lock (activate precision mode) */
requestPointerLock: () => void
/** Exit pointer lock (deactivate precision mode) */
exitPointerLock: () => void
}
/**
* Custom hook for managing pointer lock state.
*
* Handles pointer lock events and provides methods to request/exit
* pointer lock mode. The pointer lock API is used for precision mode
* in the magnifier, allowing fine-grained cursor control.
*
* @param options - Configuration options
* @returns Pointer lock state and control methods
*/
export function usePointerLock(options: UsePointerLockOptions): UsePointerLockReturn {
const { containerRef, onLockAcquired, onLockReleased } = options
const [pointerLocked, setPointerLocked] = useState(false)
// Track previous lock state for detecting transitions
const prevLockedRef = useRef(false)
// Set up pointer lock event listeners
useEffect(() => {
const handlePointerLockChange = () => {
const isLocked = document.pointerLockElement === containerRef.current
console.log('[usePointerLock] Pointer lock change event:', {
isLocked,
pointerLockElement: document.pointerLockElement,
containerElement: containerRef.current,
elementsMatch: document.pointerLockElement === containerRef.current,
})
setPointerLocked(isLocked)
// Call callbacks on state transitions
const wasLocked = prevLockedRef.current
if (isLocked && !wasLocked) {
console.log('[usePointerLock] 🔒 Pointer lock ACQUIRED')
onLockAcquired?.()
} else if (!isLocked && wasLocked) {
console.log('[usePointerLock] 🔓 Pointer lock RELEASED')
onLockReleased?.()
}
prevLockedRef.current = isLocked
}
const handlePointerLockError = () => {
console.error('[usePointerLock] ❌ Failed to acquire pointer lock')
setPointerLocked(false)
}
document.addEventListener('pointerlockchange', handlePointerLockChange)
document.addEventListener('pointerlockerror', handlePointerLockError)
console.log('[usePointerLock] Event listeners attached')
return () => {
document.removeEventListener('pointerlockchange', handlePointerLockChange)
document.removeEventListener('pointerlockerror', handlePointerLockError)
console.log('[usePointerLock] Event listeners removed')
}
}, [containerRef, onLockAcquired, onLockReleased])
// Release pointer lock when component unmounts
useEffect(() => {
return () => {
if (document.pointerLockElement) {
console.log('[usePointerLock] Component unmounting - releasing pointer lock')
document.exitPointerLock()
}
}
}, [])
const requestPointerLock = () => {
if (containerRef.current && !pointerLocked) {
console.log('[usePointerLock] Requesting pointer lock')
containerRef.current.requestPointerLock()
}
}
const exitPointerLock = () => {
if (pointerLocked) {
console.log('[usePointerLock] Exiting pointer lock')
document.exitPointerLock()
}
}
return {
pointerLocked,
requestPointerLock,
exitPointerLock,
}
}