import {EventBus} from '@/core/events/EventBus'; import type { SankeyButtonEventData, SankeyElementInteractionEventData, SankeyKeypressEventData, SankeySliderEventData } from '@/core/types/public-events'; import {DataService} from "@/services/data/DataService"; import {AnimationService} from "@/services/AnimationService"; import {Logger} from "@/utils/Logger"; // ==================== INTERACTION INTERFACES ==================== type D3SVGSelection = d3.Selection; type D3DivSelection = d3.Selection; interface InteractionState { isMouseDown: boolean; lastMousePosition: { x: number; y: number }; selectedElement: Element | null; isDragging: boolean; touchStartTime: number; keyboardShortcutsEnabled: boolean; } interface InteractionHandlers { onElementHover?: (element: Element, event: MouseEvent) => void; onElementClick?: (element: Element, event: MouseEvent) => void; onKeyboardNavigation?: (key: string, event: KeyboardEvent) => void; onSliderInteraction?: (value: number, event: Event) => void; } /** * Interaction Service - Advanced User Interface & Accessibility Management * * ARCHITECTURAL RESPONSIBILITY: Comprehensive User Interaction & Accessibility Framework * * This service implements sophisticated interaction patterns for multi-platform energy * visualization, providing seamless mouse, touch, keyboard, and accessibility support. * Ensures inclusive design principles and responsive user experience across all devices. * * ADVANCED INTERACTION PATTERNS: * 1. **Multi-Modal Input**: Mouse, touch, and keyboard with context switching * 2. **Event Delegation**: Efficient handling of dynamic SVG elements * 3. **Accessibility Integration**: WCAG 2.1 compliance with screen reader support * 4. **Cross-Platform Compatibility**: Desktop, tablet, and mobile optimization * 5. **Performance Optimization**: Event debouncing and efficient listener management * 6. **State Management**: Centralized interaction state for coordinated responses * * USER EXPERIENCE ARCHITECTURE: * - **Mouse Interactions**: Precise hover, click, and drag operations * - **Touch Interactions**: Gesture recognition with mobile-optimized responses * - **Keyboard Navigation**: Full accessibility with arrow keys, space, enter * - **Screen Reader Support**: ARIA labels and semantic markup integration * - **Visual Feedback**: Immediate response to all user interactions * * ACCESSIBILITY FEATURES: * - Keyboard-only navigation through all interactive elements * - Screen reader compatibility with descriptive ARIA labels * - High contrast support and focus management * - Mobile accessibility with proper touch target sizing * - Semantic HTML structure for assistive technologies * * EVENT-DRIVEN INTEGRATION: * Communicates through event bus for loose coupling with other services, * enabling coordinated responses without direct dependencies. */ export class InteractionService { // INTERACTION STATE MANAGEMENT: Centralized multi-modal input tracking // Maintains state for mouse, touch, keyboard, and accessibility interactions private state: InteractionState = { isMouseDown: false, // Mouse button state for drag detection lastMousePosition: {x: 0, y: 0}, // Cursor position for tooltip positioning selectedElement: null, // Currently focused/selected element isDragging: false, // Drag operation state flag touchStartTime: 0, // Touch gesture timing for tap vs drag keyboardShortcutsEnabled: true // Global keyboard accessibility state }; // INTERACTION HANDLER REGISTRY: Customizable interaction callbacks // Enables flexible response patterns for different interaction types private handlers: InteractionHandlers = {}; // EVENT LISTENER MANAGEMENT: Centralized cleanup for memory leak prevention // Tracks all registered listeners for proper disposal during service cleanup private eventListeners: Array<{ element: Element | Document | Window; event: string; handler: EventListener }> = []; constructor( private animationControlService: AnimationService, private dataService: DataService, private eventBus: EventBus, private logger: Logger, ) { // INTERACTION SERVICE READY: Multi-platform user interface management initialized // All interaction patterns and accessibility features are now available } /** * Initialize Comprehensive Multi-Platform User Interactions * * INITIALIZATION RESPONSIBILITY: Complete interaction system setup across all input modalities * * This method orchestrates the setup of all user interaction capabilities, creating * a comprehensive interface layer that supports mouse, touch, keyboard, and accessibility * interactions for the energy visualization. * * MULTI-MODAL INTERACTION INITIALIZATION: * 1. **Mouse Interactions**: Hover effects, click handling, drag operations * 2. **Touch Interactions**: Mobile gestures with responsive feedback * 3. **Keyboard Navigation**: Full accessibility with arrow key navigation * 4. **Slider Controls**: Timeline interaction with precise positioning * 5. **Button Controls**: Play/pause and control button event handling * 6. **Accessibility Features**: WCAG 2.1 compliance with screen reader support * * CROSS-PLATFORM COMPATIBILITY: * Automatically detects device capabilities (touch support, keyboard availability) * and adapts interaction patterns for optimal user experience on each platform. * * EVENT SYSTEM INTEGRATION: * Broadcasts initialization completion to coordinate with other services, * enabling system-wide awareness of interaction capability readiness. */ public initializeInteractions(svg: D3SVGSelection, tooltip: D3DivSelection): void { this.logger.log('InteractionService: Initializing comprehensive multi-platform interactions...'); // MOUSE INTERACTION SYSTEM: Desktop precision interactions // Handles hover effects, precise clicking, and drag operations this.setupMouseInteractions(svg, tooltip); // TOUCH INTERACTION SYSTEM: Mobile-optimized gesture recognition // Provides responsive touch feedback and mobile-friendly interactions this.setupTouchInteractions(svg, tooltip); // KEYBOARD ACCESSIBILITY SYSTEM: Full navigation without mouse // Enables complete visualization control through keyboard shortcuts this.enableKeyboardNavigation(); // TIMELINE SLIDER SYSTEM: Interactive temporal navigation // Provides precise year selection and smooth timeline scrubbing this.setupSliderInteractions(); // CONTROL BUTTON SYSTEM: Play/pause and interface controls // Handles all button interactions with proper state management this.setupButtonInteractions(); // ACCESSIBILITY COMPLIANCE SYSTEM: WCAG 2.1 standards implementation // Ensures screen reader compatibility and inclusive design // this.setupAccessibilityFeatures(svg); // EVENT SYSTEM NOTIFICATION: Broadcast interaction readiness // Enables coordinated initialization across the visualization system this.eventBus.emit({ type: 'system.initialized', timestamp: Date.now(), source: 'InteractionService', data: { mouseEnabled: true, // Desktop mouse interactions active touchEnabled: 'ontouchstart' in window, // Touch capability detection keyboardEnabled: this.state.keyboardShortcutsEnabled // Accessibility navigation active } }); this.logger.log('InteractionService: All interaction modalities initialized successfully'); } /** * Set up mouse event handlers * Provides hover effects, click handling, and drag support */ private setupMouseInteractions(svg: D3SVGSelection, tooltip: D3DivSelection): void { // SVG mouse events const svgElement = svg.node(); if (!svgElement) return; // Mouse move for tooltips and hover effects const mouseMoveHandler = (event: MouseEvent) => { this.state.lastMousePosition = {x: event.clientX, y: event.clientY}; // Handle element hovering const target = event.target as Element; if (target && (target.classList.contains('evs-flow') || target.classList.contains('evs-box'))) { this.handleElementHover(target, event); } }; // Mouse click for element selection const mouseClickHandler = (event: MouseEvent) => { const target = event.target as Element; if (target) { this.handleElementClick(target, event); } }; // Mouse down for drag start const mouseDownHandler = (event: MouseEvent) => { this.state.isMouseDown = true; this.state.selectedElement = event.target as Element; }; // Mouse up for drag end const mouseUpHandler = () => { this.state.isMouseDown = false; this.state.selectedElement = null; this.state.isDragging = false; }; // Add event listeners this.addEventListener(svgElement, 'mousemove', mouseMoveHandler as EventListener); this.addEventListener(svgElement, 'click', mouseClickHandler as EventListener); this.addEventListener(svgElement, 'mousedown', mouseDownHandler as EventListener); this.addEventListener(document, 'mouseup', mouseUpHandler); this.logger.log('InteractionService: Mouse interactions enabled'); } /** * Set up touch event handlers * Provides mobile-friendly touch interactions */ private setupTouchInteractions(svg: D3SVGSelection, tooltip: D3DivSelection): void { const svgElement = svg.node(); if (!svgElement || !('ontouchstart' in window)) return; // Touch start const touchStartHandler = (event: TouchEvent) => { this.state.touchStartTime = Date.now(); const touch = event.touches[0]; if (touch) { this.state.lastMousePosition = {x: touch.clientX, y: touch.clientY}; } }; // Touch end - handle as click if short duration const touchEndHandler = (event: TouchEvent) => { const touchDuration = Date.now() - this.state.touchStartTime; // Treat short touches as clicks if (touchDuration < 300) { const touch = event.changedTouches[0]; if (touch) { const target = document.elementFromPoint(touch.clientX, touch.clientY); if (target) { // Create synthetic mouse event for compatibility const syntheticEvent = new MouseEvent('click', { clientX: touch.clientX, clientY: touch.clientY, bubbles: true }); this.handleElementClick(target, syntheticEvent); } } } }; // Touch move for dragging const touchMoveHandler = (event: TouchEvent) => { event.preventDefault(); // Prevent scrolling const touch = event.touches[0]; if (touch) { this.state.lastMousePosition = {x: touch.clientX, y: touch.clientY}; } }; // Add touch event listeners this.addEventListener(svgElement, 'touchstart', touchStartHandler as EventListener); this.addEventListener(svgElement, 'touchend', touchEndHandler as EventListener); this.addEventListener(svgElement, 'touchmove', touchMoveHandler as EventListener); this.logger.log('InteractionService: Touch interactions enabled'); } /** * Enable keyboard navigation shortcuts * * **Accessibility Enhancement Responsibility:** * - WCAG 2.1 AA compliance for keyboard navigation * - Document-level keyboard event delegation * - Form input safety (prevents interference) * - Global shortcut system activation * * **Keyboard Navigation Architecture:** * - Document Event Delegation: Single handler for all keyboard events * - Input Safety: Excludes form fields from shortcut processing * - State Management: Prevents duplicate handler registration * - Event System Integration: Coordinates with EventBus * * **Performance Optimization:** * - Single document listener (efficient delegation pattern) * - Early return for duplicate activation * - Form input filtering (performance + UX) * * **Accessibility Standards:** * - Follows ARIA keyboard navigation patterns * - Provides alternative to mouse interaction * - Ensures consistent cross-platform behavior */ public enableKeyboardNavigation(): void { if (this.state.keyboardShortcutsEnabled) return; const keydownHandler = (event: KeyboardEvent) => { // Don't interfere with form inputs - accessibility best practice // Prevents shortcuts from disrupting user typing in form fields if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement || event.target instanceof HTMLSelectElement) { return; } this.handleKeyboardNavigation(event); }; this.addEventListener(document, 'keydown', keydownHandler as EventListener); this.state.keyboardShortcutsEnabled = true; this.logger.log('InteractionService: Keyboard navigation enabled'); } /** * Disable keyboard navigation * * **Resource Management Responsibility:** * - Graceful keyboard navigation shutdown * - State cleanup coordination * - Memory leak prevention (handlers cleaned by cleanup()) * * **Accessibility Pattern:** * - Allows dynamic keyboard navigation toggling * - Maintains system state consistency * - Prepares for service disposal */ public disableKeyboardNavigation(): void { this.state.keyboardShortcutsEnabled = false; // Event listeners will be cleaned up by cleanup() method this.logger.log('InteractionService: Keyboard navigation disabled'); } /** * Handle keyboard navigation events * * **Keyboard Shortcuts Responsibility:** * - Comprehensive animation control via keyboard * - Standard accessibility key mapping * - Cross-platform keyboard compatibility * - Event delegation and custom handler integration * * **Standard Keyboard Mappings (WCAG 2.1 AA):** * - Space/Enter: Play/Pause toggle (standard media controls) * - Arrow Left/Right: Year navigation (standard timeline controls) * - Home/End: First/Last year navigation (standard list navigation) * - Escape: Pause/Stop action (standard escape behavior) * * **Event-Driven Architecture Integration:** * - Emits structured keyboard events to EventBus * - Captures modifier keys (Ctrl, Shift, Alt) for advanced interactions * - Integrates with AnimationService for timeline control * - Supports custom keyboard handlers via callback pattern * * **Accessibility Design Principles:** * - Follows operating system keyboard conventions * - Provides equivalent functionality to mouse interactions * - Preventable default behavior (event.preventDefault()) * - Comprehensive modifier key support for power users */ private handleKeyboardNavigation(event: KeyboardEvent): void { const key = event.key.toLowerCase(); // Animation controls - Standard accessibility keyboard shortcuts // Following WCAG 2.1 AA guidelines for media and timeline controls switch (key) { case ' ': case 'enter': // Space or Enter: Toggle play/pause (standard media control) // Equivalent to clicking play button - primary interaction event.preventDefault(); if (this.animationControlService.isPlaying()) { this.animationControlService.pause(); } else { this.animationControlService.play(); } break; case 'arrowleft': // Left arrow: Previous year (standard timeline navigation) // Provides granular control for year-by-year analysis event.preventDefault(); this.animationControlService.previousYear(); break; case 'arrowright': // Right arrow: Next year (standard timeline navigation) // Provides granular control for year-by-year analysis event.preventDefault(); this.animationControlService.nextYear(); break; case 'home': // Home: Go to first year (standard list/timeline navigation) // Quick jump to beginning of energy data timeline event.preventDefault(); this.animationControlService.setYear(this.dataService.firstYear); break; case 'end': // End: Go to last year (standard list/timeline navigation) // Quick jump to most recent energy data point event.preventDefault(); this.animationControlService.setYear(this.dataService.lastYear); break; case 'escape': // Escape: Pause animation (standard escape behavior) // Emergency stop functionality - accessibility requirement event.preventDefault(); this.animationControlService.pause(); break; } // Emit keyboard navigation event - Event-Driven Architecture Integration // Enables other services to react to keyboard interactions // Provides rich event context (key + modifiers) for advanced interactions const keypressData: SankeyKeypressEventData = { key, // Primary key pressed (normalized to lowercase) ctrlKey: event.ctrlKey, // Enables Ctrl+key shortcuts shiftKey: event.shiftKey, // Enables Shift+key shortcuts altKey: event.altKey // Enables Alt+key shortcuts (power user features) }; this.eventBus.emit({ type: 'interaction.keypress', timestamp: Date.now(), source: 'InteractionService', data: keypressData }); // Call custom handler if provided - Extensibility Pattern // Allows external code to extend keyboard navigation behavior // Supports application-specific keyboard shortcuts beyond standard set if (this.handlers.onKeyboardNavigation) { this.handlers.onKeyboardNavigation(key, event); } } /** * Set up slider interactions * * **Timeline Control Responsibility:** * - Direct year selection via range slider * - Real-time year value feedback * - Smooth animation coordination * - Event-driven timeline synchronization * * **User Control Patterns:** * - HTML5 range input integration (native accessibility) * - Continuous value updates (smooth interaction) * - Animation service coordination (timeline sync) * - Custom event emission (extensibility) * * **Accessibility Features:** * - Native keyboard support (arrow keys, page up/down) * - Screen reader compatibility (range slider semantics) * - Touch-friendly interaction (mobile/tablet support) * - Visual feedback during interaction * * **Performance Considerations:** * - Direct DOM element access (cached reference) * - Efficient value parsing (parseFloat optimization) * - Event delegation pattern (single handler) */ private setupSliderInteractions(): void { const rangeSlider = document.getElementById('evs-range-slider') as HTMLInputElement; if (!rangeSlider) return; const sliderInputHandler = (event: Event) => { const target = event.target as HTMLInputElement; const year = parseFloat(target.value); // Convert string value to numeric year // Update animation to selected year - Direct timeline control // Triggers visualization update to show energy data for selected year this.animationControlService.setYear(year); // Emit slider interaction event - Event-Driven Architecture // Enables other services to react to timeline position changes // Provides structured event data for logging and analytics const sliderData: SankeySliderEventData = { year, // Selected year value (numeric) value: year // Duplicate for backwards compatibility }; this.eventBus.emit({ type: 'interaction.slider', timestamp: Date.now(), source: 'InteractionService', data: sliderData }); // Call custom handler if provided - Extensibility Pattern // Supports application-specific slider interaction behavior // Enables custom year selection logic or additional UI updates if (this.handlers.onSliderInteraction) { this.handlers.onSliderInteraction(year, event); } }; this.addEventListener(rangeSlider, 'input', sliderInputHandler); this.logger.log('InteractionService: Slider interactions enabled'); } /** * Set up button interactions * * **Control Button Responsibility:** * - Play/pause animation control * - Speed adjustment controls * - Animation state management * - User feedback coordination * * **User Interface Patterns:** * - Primary action button (play/pause toggle) * - Secondary action buttons (speed controls) * - Event delegation for button groups * - State-aware button behavior * * **Accessibility Integration:** * - Click event handling (mouse + keyboard activation) * - Focus management (keyboard navigation) * - Screen reader button semantics * - Touch-friendly tap targets * * **Animation Control Architecture:** * - Direct AnimationService integration * - State synchronization (playing/paused) * - Speed control coordination * - Event emission for system coordination */ private setupButtonInteractions(): void { // Play/pause button - Primary animation control const playButton = document.getElementById('evs-play-button'); if (playButton) { const playButtonHandler = (event: Event) => { event.preventDefault(); // Prevent default button behavior // Toggle animation state - Primary user interaction // Provides immediate visual feedback through state change if (this.animationControlService.isPlaying()) { this.animationControlService.pause(); } else { this.animationControlService.play(); } // Emit button interaction event - Event-Driven Architecture // Enables other services to react to animation state changes // Note: Action reflects the RESULT of the button press, not the current state const playButtonData: SankeyButtonEventData = { buttonId: 'evs-play-button', action: this.animationControlService.isPlaying() ? 'play' : 'pause' }; this.eventBus.emit({ type: 'interaction.button', timestamp: Date.now(), source: 'InteractionService', data: playButtonData }); }; this.addEventListener(playButton, 'click', playButtonHandler); } // Speed control buttons (if they exist) - Secondary animation controls // Supports variable animation speeds for different analysis needs const speedButtons = document.querySelectorAll('[data-speed]'); speedButtons.forEach(button => { const speedButtonHandler = (event: Event) => { const target = event.target as HTMLElement; const speed = parseInt(target.dataset.speed || '200'); // Default 200ms if no speed specified // Update animation speed - Performance and UX customization // Allows users to adjust viewing pace for their analysis needs this.animationControlService.setSpeed(speed); // Emit speed change event - Event-Driven Architecture // Enables UI updates (active button state, speed indicator) const speedButtonData: SankeyButtonEventData = { buttonId: target.id, action: 'speed-change', speed // New animation speed in milliseconds }; this.eventBus.emit({ type: 'interaction.button', timestamp: Date.now(), source: 'InteractionService', data: speedButtonData }); }; this.addEventListener(button, 'click', speedButtonHandler); }); this.logger.log('InteractionService: Button interactions enabled'); } /** * Set up accessibility features * * **WCAG 2.1 AA Compliance Responsibility:** * - ARIA labels and semantic roles * - Keyboard focus management * - Screen reader support * - Visual focus indicators * - Interactive element accessibility * * **Accessibility Standards Implementation:** * - SVG accessibility (role="img", aria-label) * - Keyboard navigation (tabindex, focus/blur) * - Control accessibility (slider, button roles) * - Focus indicators (visual outline feedback) * - Screen reader descriptions (meaningful labels) * * **Universal Design Principles:** * - Perceivable: Visual focus indicators, semantic structure * - Operable: Keyboard navigation, touch targets * - Understandable: Clear labels, consistent behavior * - Robust: Cross-platform compatibility, assistive technology support * * **Focus Management Architecture:** * - Logical tab order (sequential navigation) * - Visual focus indicators (CSS outline styling) * - Focus trap prevention (proper event handling) * - Screen reader announcements (ARIA integration) */ private setupAccessibilityFeatures(svg: D3SVGSelection): void { const svgElement = svg.node(); if (!svgElement) return; // Add ARIA label to SVG - Screen Reader Support // Provides semantic meaning for complex data visualization svgElement.setAttribute('role', 'img'); svgElement.setAttribute('aria-label', 'Interactive U.S. Energy Usage Sankey Diagram'); // Add tabindex for keyboard navigation - Focus Management // Makes SVG focusable for keyboard users (WCAG 2.1 requirement) svgElement.setAttribute('tabindex', '0'); // Add focus indicators - Visual Accessibility // Provides clear visual feedback when SVG receives keyboard focus const focusHandler = () => { svgElement.style.outline = '2px solid #007cba'; // High contrast focus indicator }; const blurHandler = () => { svgElement.style.outline = 'none'; // Remove outline when focus lost }; this.addEventListener(svgElement, 'focus', focusHandler); this.addEventListener(svgElement, 'blur', blurHandler); // Add accessibility to controls - Control Semantic Enhancement // Range slider accessibility - Timeline control semantics const rangeSlider = document.getElementById('evs-range-slider'); if (rangeSlider) { rangeSlider.setAttribute('aria-label', 'Select year for energy data visualization'); rangeSlider.setAttribute('role', 'slider'); // Explicit slider semantics } // Play button accessibility - Animation control semantics const playButton = document.getElementById('evs-play-button'); if (playButton) { playButton.setAttribute('aria-label', 'Play or pause animation'); playButton.setAttribute('role', 'button'); // Explicit button semantics } this.logger.log('InteractionService: Accessibility features enabled'); } /** * Handle element hover events * * **Visual Feedback Responsibility:** * - Interactive element highlighting * - Hover state management * - Multi-element coordination (exclusive hover) * - Event-driven hover notifications * * **User Experience Patterns:** * - Visual Affordance: Immediate hover feedback via CSS classes * - Exclusive Interaction: Single element hover state (removes others) * - Event Context: Rich hover event data (element type, fuel, sector) * - Mouse Position: Coordinates for tooltip positioning * * **Interaction Architecture:** * - CSS Class-Based Styling: .hovered class for visual feedback * - Event Bus Integration: Structured hover events for system coordination * - Custom Handler Support: Extensible hover behavior * - Element Classification: Flow vs Box element detection * * **Performance Considerations:** * - Efficient DOM querying (.hovered class removal) * - Minimal DOM manipulation (add/remove classes) * - Event data extraction (cached attribute access) */ private handleElementHover(element: Element, event: MouseEvent): void { // Add hover class for styling - Visual Feedback // Triggers CSS styles for immediate visual response element.classList.add('hovered'); // Remove hover class from other elements - Exclusive Hover Pattern // Ensures only one element shows hover state at a time (better UX) document.querySelectorAll('.hovered').forEach(el => { if (el !== element) { el.classList.remove('hovered'); } }); // Emit hover event - Event-Driven Architecture // Provides rich context for tooltip display, analytics, and custom behavior const hoverData: SankeyElementInteractionEventData = { elementType: element.classList.contains('evs-flow') ? 'flow' : 'box', // Element classification fuel: element.getAttribute('data-fuel'), // Energy source identification sector: element.getAttribute('data-sector'), // Consumption sector identification mousePosition: {x: event.clientX, y: event.clientY} // Coordinates for tooltip positioning }; this.eventBus.emit({ type: 'interaction.hover', timestamp: Date.now(), source: 'InteractionService', data: hoverData }); // Call custom handler if provided - Extensibility Pattern // Supports application-specific hover behavior // Enables custom tooltip content, highlighting, or data display if (this.handlers.onElementHover) { this.handlers.onElementHover(element, event); } } /** * Handle element click events * * **Selection Management Responsibility:** * - Element selection state management * - Exclusive selection pattern (single selection) * - Visual selection feedback * - Event-driven selection notifications * * **User Interaction Patterns:** * - Click-to-Select: Primary selection mechanism * - Exclusive Selection: Single element selected at a time * - Visual Feedback: .selected class for styling * - Context Preservation: Rich click event data * * **State Management Architecture:** * - CSS Class-Based Selection: .selected class for visual state * - DOM State Management: Clear previous selections * - Event Data Capture: Element type, fuel, sector, coordinates * - Extensible Handler Support: Custom click behavior * * **Integration Benefits:** * - Analytics Support: Click tracking and user behavior * - Tooltip Coordination: Click-based detailed information display * - Custom Behavior: Application-specific selection actions */ private handleElementClick(element: Element, event: MouseEvent): void { // Clear all previous selections and add selected class - Exclusive Selection Pattern // Ensures only one element is selected at a time for focused analysis // document.querySelectorAll('.selected').forEach(el => el.classList.remove('selected')); // element.classList.add('selected'); // Emit click event - Event-Driven Architecture // Provides comprehensive click context for analytics, tooltips, and custom behavior const clickData: SankeyElementInteractionEventData = { elementType: element.classList.contains('evs-flow') ? 'flow' : 'box', // Element classification fuel: element.getAttribute('data-fuel'), // Energy source identification sector: element.getAttribute('data-sector'), // Consumption sector identification mousePosition: {x: event.clientX, y: event.clientY} // Click coordinates for UI positioning }; this.eventBus.emit({ type: 'interaction.click', timestamp: Date.now(), source: 'InteractionService', data: clickData }); // Call custom handler if provided - Extensibility Pattern // Supports application-specific click behavior // Enables detailed data display, drill-down functionality, or custom actions if (this.handlers.onElementClick) { this.handlers.onElementClick(element, event); } } /** * Set custom interaction handlers */ public setInteractionHandlers(handlers: InteractionHandlers): void { this.handlers = {...this.handlers, ...handlers}; this.logger.log('InteractionService: Custom handlers updated'); } /** * Helper method to add event listener and track for cleanup */ private addEventListener( element: Element | Document | Window, event: string, handler: EventListener ): void { element.addEventListener(event, handler); this.eventListeners.push({element, event, handler}); } /** * Clean up all interactions and event listeners * * **Resource Management Responsibility:** * - Complete memory cleanup and leak prevention * - Event listener disposal * - State reset and consistency * - DOM class cleanup * * **Memory Management Architecture:** * - Event Listener Cleanup: Remove all tracked listeners to prevent leaks * - State Reset: Return to initial state for consistent disposal * - Handler Clearing: Remove all custom handlers * - DOM Cleanup: Remove visual state classes (.hovered, .selected) * * **Cleanup Pattern Benefits:** * - Memory Leak Prevention: Proper event listener disposal * - State Consistency: Clean slate for reinitialization * - Resource Optimization: Free unused memory and references * - Visual Cleanup: Remove transient UI states * * **Architecture Integration:** * - Service Lifecycle: Proper service disposal pattern * - Event-Driven Cleanup: Coordinated with other services * - DOM State Management: Visual consistency maintenance * - Performance Optimization: Resource deallocation */ public cleanup(): void { // Remove all event listeners - Memory Leak Prevention // Critical: Dispose of all tracked event listeners to prevent memory leaks this.eventListeners.forEach(({element, event, handler}) => { element.removeEventListener(event, handler); }); this.eventListeners = []; // Clear tracking array // Reset state - State Consistency // Return to initial state for clean service disposal/reinitialization this.state = { isMouseDown: false, lastMousePosition: {x: 0, y: 0}, selectedElement: null, isDragging: false, touchStartTime: 0, keyboardShortcutsEnabled: false }; // Clear handlers - Reference Cleanup // Remove all custom handler references to prevent memory retention this.handlers = {}; // Remove hover and selected classes - Visual State Cleanup // Clean up transient visual states from DOM elements document.querySelectorAll('.hovered, .selected').forEach(el => { el.classList.remove('hovered', 'selected'); }); this.logger.log('InteractionService: Cleanup completed'); } // ==================== UTILITY METHODS ==================== /** * Get current interaction state */ public getInteractionState(): Readonly { return {...this.state}; } /** * Check if interactions are enabled */ public isInteractionEnabled(): boolean { return this.eventListeners.length > 0; } /** * Get interaction statistics */ public getInteractionStats(): { eventListeners: number; keyboardEnabled: boolean; touchSupported: boolean; lastInteraction: { x: number; y: number }; } { return { eventListeners: this.eventListeners.length, keyboardEnabled: this.state.keyboardShortcutsEnabled, touchSupported: 'ontouchstart' in window, lastInteraction: this.state.lastMousePosition }; } /** * Programmatically trigger element hover */ public triggerElementHover(element: Element): void { if (element) { const syntheticEvent = new MouseEvent('mouseover', { bubbles: true, clientX: this.state.lastMousePosition.x, clientY: this.state.lastMousePosition.y }); this.handleElementHover(element, syntheticEvent); } } /** * Programmatically trigger element click */ public triggerElementClick(element: Element): void { if (element) { const syntheticEvent = new MouseEvent('click', { bubbles: true, clientX: this.state.lastMousePosition.x, clientY: this.state.lastMousePosition.y }); this.handleElementClick(element, syntheticEvent); } } }