@yhattav/react-component-cursor
Version:
A lightweight, TypeScript-first React library for creating beautiful custom cursors with SSR support, smooth animations, and zero dependencies. Perfect for interactive websites, games, and creative applications.
1 lines • 16.3 kB
Source Map (JSON)
{"version":3,"sources":["../src/utils/MouseTracker.ts","../src/utils/CursorCoordinator.ts"],"names":["_MouseTracker","subscription","isSSR","subscriberState","id","event","newPosition","currentTime","lastCallTime","timeoutId","throttleMs","timeSinceLastCall","stored","e","__spreadValues","MouseTracker","_CursorCoordinator","mouseUnsubscribe","position","subscriptionId","CursorCoordinator"],"mappings":"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","file":"CursorCoordinator-M32HDHIO.mjs","sourcesContent":["import { isSSR } from './ssr';\n\n// Types for the mouse tracker\nexport interface MouseSubscription {\n id: string;\n callback: (position: { x: number; y: number }) => void;\n throttleMs?: number;\n}\n\ninterface SubscriberState {\n subscription: MouseSubscription;\n lastCallTime: number;\n timeoutId: NodeJS.Timeout | null;\n}\n\nclass MouseTracker {\n private static instance: MouseTracker | null = null;\n private subscribers = new Map<string, SubscriberState>();\n private currentPosition: { x: number; y: number } | null = null;\n private isListening = false;\n private rafId: number | null = null;\n\n private constructor() {\n this.handleMouseMove = this.handleMouseMove.bind(this);\n }\n\n static getInstance(): MouseTracker {\n if (!MouseTracker.instance) {\n MouseTracker.instance = new MouseTracker();\n }\n return MouseTracker.instance;\n }\n\n subscribe(subscription: MouseSubscription): () => void {\n // Skip subscription during SSR\n if (isSSR()) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {}; // No-op cleanup\n }\n\n const subscriberState: SubscriberState = {\n subscription,\n lastCallTime: 0,\n timeoutId: null,\n };\n\n this.subscribers.set(subscription.id, subscriberState);\n \n // Start listening if this is the first subscriber\n if (!this.isListening) {\n this.startListening();\n }\n\n // Immediately notify new subscriber if we have a current position\n if (this.currentPosition) {\n // Use setTimeout to avoid synchronous callback during subscription\n setTimeout(() => {\n if (this.subscribers.has(subscription.id)) {\n this.callSubscriber(subscriberState);\n }\n }, 0);\n }\n\n // Return unsubscribe function\n return () => {\n this.unsubscribe(subscription.id);\n };\n }\n\n private unsubscribe(id: string): void {\n const subscriberState = this.subscribers.get(id);\n if (subscriberState?.timeoutId) {\n clearTimeout(subscriberState.timeoutId);\n }\n \n this.subscribers.delete(id);\n \n // Stop listening if no more subscribers\n if (this.subscribers.size === 0) {\n this.stopListening();\n }\n }\n\n private startListening(): void {\n if (this.isListening || isSSR()) return;\n \n document.addEventListener('mousemove', this.handleMouseMove);\n this.isListening = true;\n \n // Store position globally for persistence across navigation\n this.loadGlobalPosition();\n }\n\n private stopListening(): void {\n if (!this.isListening) return;\n \n document.removeEventListener('mousemove', this.handleMouseMove);\n this.isListening = false;\n \n if (this.rafId) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n }\n\n private handleMouseMove(event: MouseEvent): void {\n const newPosition = { x: event.clientX, y: event.clientY };\n \n this.currentPosition = newPosition;\n this.saveGlobalPosition();\n \n // Use RAF to batch notifications for better performance\n if (this.rafId === null) {\n this.rafId = requestAnimationFrame(() => {\n this.notifySubscribers();\n this.rafId = null;\n });\n }\n }\n\n private notifySubscribers(): void {\n // Don't notify if we don't have a position yet\n if (!this.currentPosition) return;\n \n const currentTime = Date.now();\n \n this.subscribers.forEach((subscriberState) => {\n const { subscription, lastCallTime, timeoutId } = subscriberState;\n const { throttleMs = 0 } = subscription;\n\n // Apply throttling if specified\n if (throttleMs > 0) {\n const timeSinceLastCall = currentTime - lastCallTime;\n \n if (timeSinceLastCall >= throttleMs) {\n // Call immediately\n this.callSubscriber(subscriberState);\n } else {\n // Schedule delayed call\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n \n subscriberState.timeoutId = setTimeout(() => {\n this.callSubscriber(subscriberState);\n subscriberState.timeoutId = null;\n }, throttleMs - timeSinceLastCall);\n }\n } else {\n // No throttling, call immediately\n this.callSubscriber(subscriberState);\n }\n });\n }\n\n private callSubscriber(subscriberState: SubscriberState): void {\n if (!this.currentPosition) return;\n \n subscriberState.subscription.callback(this.currentPosition);\n subscriberState.lastCallTime = Date.now();\n }\n\n\n\n private loadGlobalPosition(): void {\n // Try to load position from global storage for persistence across navigation\n try {\n const stored = (window as any).__mouseTrackerPosition__;\n if (stored && typeof stored.x === 'number' && typeof stored.y === 'number') {\n this.currentPosition = stored;\n }\n } catch {\n // Ignore errors when accessing global position\n }\n }\n\n private saveGlobalPosition(): void {\n // Save current position globally for persistence\n if (this.currentPosition) {\n try {\n (window as any).__mouseTrackerPosition__ = { ...this.currentPosition };\n } catch {\n // Ignore errors when saving global position\n }\n }\n }\n\n // Public method to get current position (useful for initial positioning)\n getCurrentPosition(): { x: number; y: number } | null {\n return this.currentPosition ? { ...this.currentPosition } : null;\n }\n\n // For testing/debugging\n getSubscriberCount(): number {\n return this.subscribers.size;\n }\n\n // Cleanup method for testing\n static resetInstance(): void {\n if (MouseTracker.instance) {\n MouseTracker.instance.stopListening();\n MouseTracker.instance = null;\n }\n }\n}\n\nexport { MouseTracker }; ","import { isSSR } from './ssr';\nimport { MouseTracker } from './MouseTracker';\n\nexport interface CursorSubscription {\n id: string;\n onPositionChange: (position: { x: number; y: number }) => void;\n throttleMs?: number;\n}\n\ninterface CoordinatorSubscriberState {\n subscription: CursorSubscription;\n lastCallTime: number;\n timeoutId: NodeJS.Timeout | null;\n}\n\nclass CursorCoordinator {\n private static instance: CursorCoordinator | null = null;\n private subscribers = new Map<string, CoordinatorSubscriberState>();\n private mouseTracker: MouseTracker;\n private currentGlobalPosition: { x: number; y: number } | null = null;\n private isListening = false;\n private rafId: number | null = null;\n\n private constructor() {\n this.mouseTracker = MouseTracker.getInstance();\n this.handleLayoutChange = this.handleLayoutChange.bind(this);\n }\n\n static getInstance(): CursorCoordinator {\n if (!CursorCoordinator.instance) {\n CursorCoordinator.instance = new CursorCoordinator();\n }\n return CursorCoordinator.instance;\n }\n\n subscribe(subscription: CursorSubscription): () => void {\n if (isSSR()) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {};\n }\n\n const subscriberState: CoordinatorSubscriberState = {\n subscription,\n lastCallTime: 0,\n timeoutId: null,\n };\n\n this.subscribers.set(subscription.id, subscriberState);\n \n // Start listening if this is the first subscriber\n if (!this.isListening) {\n this.startListening();\n }\n\n // Subscribe to mouse tracker for position updates\n const mouseUnsubscribe = this.mouseTracker.subscribe({\n id: `coordinator-${subscription.id}`,\n callback: (position) => {\n this.updateGlobalPosition(position);\n this.notifyPositionChange(subscription.id, position);\n },\n throttleMs: subscription.throttleMs,\n });\n\n // Return combined unsubscribe function\n return () => {\n mouseUnsubscribe();\n this.unsubscribe(subscription.id);\n };\n }\n\n private unsubscribe(id: string): void {\n const subscriberState = this.subscribers.get(id);\n if (subscriberState?.timeoutId) {\n clearTimeout(subscriberState.timeoutId);\n }\n \n this.subscribers.delete(id);\n \n // Stop listening if no more subscribers\n if (this.subscribers.size === 0) {\n this.stopListening();\n }\n }\n\n private startListening(): void {\n if (this.isListening || isSSR()) return;\n \n // Listen for layout-changing events\n document.addEventListener('scroll', this.handleLayoutChange, true);\n window.addEventListener('resize', this.handleLayoutChange);\n this.isListening = true;\n }\n\n private stopListening(): void {\n if (!this.isListening) return;\n \n document.removeEventListener('scroll', this.handleLayoutChange, true);\n window.removeEventListener('resize', this.handleLayoutChange);\n this.isListening = false;\n \n if (this.rafId) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n }\n\n private handleLayoutChange(): void {\n // Batch layout change notifications using RAF\n if (this.rafId === null) {\n this.rafId = requestAnimationFrame(() => {\n this.notifyAllSubscribersWithCurrentPosition();\n this.rafId = null;\n });\n }\n }\n\n private updateGlobalPosition(position: { x: number; y: number }): void {\n this.currentGlobalPosition = position;\n }\n\n private notifyPositionChange(subscriptionId: string, position: { x: number; y: number }): void {\n const subscriberState = this.subscribers.get(subscriptionId);\n if (!subscriberState) return;\n\n subscriberState.subscription.onPositionChange(position);\n }\n\n private notifyAllSubscribersWithCurrentPosition(): void {\n if (!this.currentGlobalPosition) return;\n \n this.subscribers.forEach((subscriberState) => {\n subscriberState.subscription.onPositionChange(this.currentGlobalPosition!);\n });\n }\n\n getSubscriberCount(): number {\n return this.subscribers.size;\n }\n\n // Cleanup method for testing\n static resetInstance(): void {\n if (CursorCoordinator.instance) {\n CursorCoordinator.instance.stopListening();\n CursorCoordinator.instance = null;\n }\n }\n}\n\nexport { CursorCoordinator }; "]}