UNPKG

@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.

254 lines (252 loc) 7.46 kB
import { __spreadValues, isSSR } from "./chunk-YMONBIUG.dev.mjs"; // src/utils/MouseTracker.ts var _MouseTracker = class _MouseTracker { constructor() { this.subscribers = /* @__PURE__ */ new Map(); this.currentPosition = null; this.isListening = false; this.rafId = null; this.handleMouseMove = this.handleMouseMove.bind(this); } static getInstance() { if (!_MouseTracker.instance) { _MouseTracker.instance = new _MouseTracker(); } return _MouseTracker.instance; } subscribe(subscription) { if (isSSR()) { return () => { }; } const subscriberState = { subscription, lastCallTime: 0, timeoutId: null }; this.subscribers.set(subscription.id, subscriberState); if (!this.isListening) { this.startListening(); } if (this.currentPosition) { setTimeout(() => { if (this.subscribers.has(subscription.id)) { this.callSubscriber(subscriberState); } }, 0); } return () => { this.unsubscribe(subscription.id); }; } unsubscribe(id) { const subscriberState = this.subscribers.get(id); if (subscriberState == null ? void 0 : subscriberState.timeoutId) { clearTimeout(subscriberState.timeoutId); } this.subscribers.delete(id); if (this.subscribers.size === 0) { this.stopListening(); } } startListening() { if (this.isListening || isSSR()) return; document.addEventListener("mousemove", this.handleMouseMove); this.isListening = true; this.loadGlobalPosition(); } stopListening() { if (!this.isListening) return; document.removeEventListener("mousemove", this.handleMouseMove); this.isListening = false; if (this.rafId) { cancelAnimationFrame(this.rafId); this.rafId = null; } } handleMouseMove(event) { const newPosition = { x: event.clientX, y: event.clientY }; this.currentPosition = newPosition; this.saveGlobalPosition(); if (this.rafId === null) { this.rafId = requestAnimationFrame(() => { this.notifySubscribers(); this.rafId = null; }); } } notifySubscribers() { if (!this.currentPosition) return; const currentTime = Date.now(); this.subscribers.forEach((subscriberState) => { const { subscription, lastCallTime, timeoutId } = subscriberState; const { throttleMs = 0 } = subscription; if (throttleMs > 0) { const timeSinceLastCall = currentTime - lastCallTime; if (timeSinceLastCall >= throttleMs) { this.callSubscriber(subscriberState); } else { if (timeoutId) { clearTimeout(timeoutId); } subscriberState.timeoutId = setTimeout(() => { this.callSubscriber(subscriberState); subscriberState.timeoutId = null; }, throttleMs - timeSinceLastCall); } } else { this.callSubscriber(subscriberState); } }); } callSubscriber(subscriberState) { if (!this.currentPosition) return; subscriberState.subscription.callback(this.currentPosition); subscriberState.lastCallTime = Date.now(); } loadGlobalPosition() { try { const stored = window.__mouseTrackerPosition__; if (stored && typeof stored.x === "number" && typeof stored.y === "number") { this.currentPosition = stored; } } catch (e) { } } saveGlobalPosition() { if (this.currentPosition) { try { window.__mouseTrackerPosition__ = __spreadValues({}, this.currentPosition); } catch (e) { } } } // Public method to get current position (useful for initial positioning) getCurrentPosition() { return this.currentPosition ? __spreadValues({}, this.currentPosition) : null; } // For testing/debugging getSubscriberCount() { return this.subscribers.size; } // Cleanup method for testing static resetInstance() { if (_MouseTracker.instance) { _MouseTracker.instance.stopListening(); _MouseTracker.instance = null; } } }; _MouseTracker.instance = null; var MouseTracker = _MouseTracker; // src/utils/CursorCoordinator.ts var _CursorCoordinator = class _CursorCoordinator { constructor() { this.subscribers = /* @__PURE__ */ new Map(); this.currentGlobalPosition = null; this.isListening = false; this.rafId = null; this.mouseTracker = MouseTracker.getInstance(); this.handleLayoutChange = this.handleLayoutChange.bind(this); } static getInstance() { if (!_CursorCoordinator.instance) { _CursorCoordinator.instance = new _CursorCoordinator(); } return _CursorCoordinator.instance; } subscribe(subscription) { if (isSSR()) { return () => { }; } const subscriberState = { subscription, lastCallTime: 0, timeoutId: null }; this.subscribers.set(subscription.id, subscriberState); if (!this.isListening) { this.startListening(); } const mouseUnsubscribe = this.mouseTracker.subscribe({ id: `coordinator-${subscription.id}`, callback: (position) => { this.updateGlobalPosition(position); this.notifyPositionChange(subscription.id, position); }, throttleMs: subscription.throttleMs }); return () => { mouseUnsubscribe(); this.unsubscribe(subscription.id); }; } unsubscribe(id) { const subscriberState = this.subscribers.get(id); if (subscriberState == null ? void 0 : subscriberState.timeoutId) { clearTimeout(subscriberState.timeoutId); } this.subscribers.delete(id); if (this.subscribers.size === 0) { this.stopListening(); } } startListening() { if (this.isListening || isSSR()) return; document.addEventListener("scroll", this.handleLayoutChange, true); window.addEventListener("resize", this.handleLayoutChange); this.isListening = true; } stopListening() { if (!this.isListening) return; document.removeEventListener("scroll", this.handleLayoutChange, true); window.removeEventListener("resize", this.handleLayoutChange); this.isListening = false; if (this.rafId) { cancelAnimationFrame(this.rafId); this.rafId = null; } } handleLayoutChange() { if (this.rafId === null) { this.rafId = requestAnimationFrame(() => { this.notifyAllSubscribersWithCurrentPosition(); this.rafId = null; }); } } updateGlobalPosition(position) { this.currentGlobalPosition = position; } notifyPositionChange(subscriptionId, position) { const subscriberState = this.subscribers.get(subscriptionId); if (!subscriberState) return; subscriberState.subscription.onPositionChange(position); } notifyAllSubscribersWithCurrentPosition() { if (!this.currentGlobalPosition) return; this.subscribers.forEach((subscriberState) => { subscriberState.subscription.onPositionChange(this.currentGlobalPosition); }); } getSubscriberCount() { return this.subscribers.size; } // Cleanup method for testing static resetInstance() { if (_CursorCoordinator.instance) { _CursorCoordinator.instance.stopListening(); _CursorCoordinator.instance = null; } } }; _CursorCoordinator.instance = null; var CursorCoordinator = _CursorCoordinator; export { CursorCoordinator }; //# sourceMappingURL=CursorCoordinator-6CTTW3XY.dev.mjs.map