diff --git a/dashboard/css/expert.css b/dashboard/css/expert.css index 538c1ad..68135d8 100644 --- a/dashboard/css/expert.css +++ b/dashboard/css/expert.css @@ -17,3 +17,16 @@ .panel-content { overscroll-behavior: contain; } + +/* Ensure the Three.js canvas has touch-action: none to prevent passive listener warnings + This is set inline in app.js but also here for CSS-level consistency */ +#scene-container { + touch-action: none; +} + +canvas { + touch-action: none; + -webkit-touch-callout: none; + -webkit-user-select: none; + user-select: none; +} diff --git a/dashboard/js/controls.js b/dashboard/js/controls.js index 9015f40..a0e7a6a 100644 --- a/dashboard/js/controls.js +++ b/dashboard/js/controls.js @@ -1,12 +1,26 @@ /** - * Panel Touch Controls + * Spaxel Dashboard - Touch Controls for Panel Touch Event Handling * * Utility for preventing touch events on sidebar panels from propagating * to the Three.js canvas and triggering unintended OrbitControls actions. * + * All listeners use passive: true with stopPropagation() to prevent + * iOS Safari warnings about passive event listeners while still + * preventing event bubbling to the canvas. + * + * OrbitControls Configuration (app.js): + * - ONE: THREE.TOUCH.ROTATE (one-finger orbit) + * - TWO: THREE.TOUCH.DOLLY (pinch zoom ONLY, no pan) + * - THREE: THREE.TOUCH.PAN (three-finger pan) + * - zoomSpeed: 1.0 (standard touch zoom speed) + * - Canvas touch-action: none (prevents passive listener warnings) + * * Usage: * Controls.addPanelTouchBlocker(sidebarElement); * Controls.addPanelTouchBlocker(overlayElement); + * + * The module also provides direct access to individual event handlers + * for custom use cases. */ (function(window) { @@ -21,19 +35,108 @@ addPanelTouchBlocker: function(element) { if (!element) return; + // Use passive listeners with stopPropagation to avoid iOS warnings + // touchstart: Prevents initial touch from reaching canvas element.addEventListener('touchstart', function(e) { e.stopPropagation(); }, { passive: true }); + // touchmove: Prevents scrolling/dragging from reaching canvas element.addEventListener('touchmove', function(e) { e.stopPropagation(); - }, { passive: false }); // non-passive to allow preventDefault if needed + }, { passive: true }); + // touchend: Prevents tap release from reaching canvas element.addEventListener('touchend', function(e) { e.stopPropagation(); }, { passive: true }); + + // touchcancel: Handle system-interrupted touches + element.addEventListener('touchcancel', function(e) { + e.stopPropagation(); + }, { passive: true }); + }, + + /** + * Remove touch event blockers from an element. + * Useful for dynamic panels that are reused. + * @param {HTMLElement} element - The element to remove blockers from + */ + removePanelTouchBlocker: (function() { + // Store references to handlers to enable removal + var handlers = new WeakMap(); + + return function(element) { + if (!element) return; + + var elementHandlers = handlers.get(element); + if (elementHandlers) { + elementHandlers.forEach(function(handler) { + element.removeEventListener(handler.type, handler.fn, handler.options); + }); + handlers.delete(element); + } + }; + })(), + + /** + * Apply touch blocking to all panel elements in the DOM. + * Useful for initializing after DOM modifications. + * @param {string} selector - CSS selector for panel elements (default: all panel classes) + */ + applyToAllPanels: function(selector) { + selector = selector || '.panel-sidebar, .panel-sidebar-right, .panel-sidebar-left, .panel-overlay, .panel-modal, .modal-container, .modal-backdrop, .panel-backdrop, .sidebar-panel, .troubleshoot-modal-overlay'; + var panels = document.querySelectorAll(selector); + panels.forEach(function(panel) { + Controls.addPanelTouchBlocker(panel); + }); + return panels.length; + }, + + /** + * Check if an element has touch event blockers applied. + * @param {HTMLElement} element - The element to check + * @returns {boolean} True if blockers are present + */ + hasTouchBlocker: function(element) { + if (!element) return false; + // Check if element has the data attribute we'll add + return element.hasAttribute('data-touch-blocker'); } }; + // Add data attribute when blocker is applied (for debugging/inspection) + var originalAdd = Controls.addPanelTouchBlocker; + Controls.addPanelTouchBlocker = function(element) { + if (!element) return; + originalAdd.call(this, element); + element.setAttribute('data-touch-blocker', 'true'); + }; + + // Auto-apply to existing panels on load (with slight delay for DOM readiness) + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', function() { + setTimeout(function() { + Controls.applyToAllPanels(); + console.log('[Controls] Touch blockers applied to ' + Controls.applyToAllPanels() + ' existing panels'); + }, 100); + }); + } else { + // DOM already ready + setTimeout(function() { + var count = Controls.applyToAllPanels(); + console.log('[Controls] Touch blockers applied to ' + count + ' existing panels'); + }, 100); + } + + // Export to window window.Controls = Controls; + + // Also export to Spaxel namespace for consistency with other modules + if (!window.Spaxel) { + window.Spaxel = {}; + } + window.Spaxel.Controls = Controls; + + console.log('[Controls] Touch controls module initialized'); })(window); diff --git a/dashboard/js/panels.js b/dashboard/js/panels.js index d440b87..36a7348 100644 --- a/dashboard/js/panels.js +++ b/dashboard/js/panels.js @@ -138,13 +138,14 @@ // Add touch event listeners to prevent propagation to canvas // This prevents OrbitControls from responding to touches on the panel + // Using passive listeners with stopPropagation to avoid iOS warnings sidebarElement.addEventListener('touchstart', function(e) { e.stopPropagation(); }, { passive: true }); sidebarElement.addEventListener('touchmove', function(e) { e.stopPropagation(); - }, { passive: false }); // Non-passive to allow preventDefault if needed + }, { passive: true }); sidebarElement.addEventListener('touchend', function(e) { e.stopPropagation(); @@ -157,7 +158,7 @@ sidebarOverlay.addEventListener('touchmove', function(e) { e.stopPropagation(); - }, { passive: false }); + }, { passive: true }); sidebarOverlay.addEventListener('touchend', function(e) { e.stopPropagation(); @@ -339,13 +340,14 @@ // Add touch event listeners to prevent propagation to canvas // This prevents OrbitControls from responding to touches on the modal + // Using passive listeners with stopPropagation to avoid iOS warnings modalElement.addEventListener('touchstart', function(e) { e.stopPropagation(); }, { passive: true }); modalElement.addEventListener('touchmove', function(e) { e.stopPropagation(); - }, { passive: false }); // Non-passive to allow preventDefault if needed + }, { passive: true }); modalElement.addEventListener('touchend', function(e) { e.stopPropagation(); @@ -358,7 +360,7 @@ modalBackdrop.addEventListener('touchmove', function(e) { e.stopPropagation(); - }, { passive: false }); + }, { passive: true }); modalBackdrop.addEventListener('touchend', function(e) { e.stopPropagation(); diff --git a/dashboard/live.html b/dashboard/live.html index ecd3963..6590488 100644 --- a/dashboard/live.html +++ b/dashboard/live.html @@ -2,7 +2,7 @@ - + Spaxel Dashboard