33
44import { DashboardAPI } from './modules/api.js?v=2025.11.21.09' ;
55import { DashboardState } from './modules/state.js?v=2025.11.21.09' ;
6- import { DashboardCharts } from './modules/charts.js?v=2025.11.21.09' ;
76import { DashboardUtils } from './modules/utils.js?v=2025.11.21.09' ;
87// External services loaded dynamically when needed (lazy loading)
98
@@ -12,7 +11,6 @@ class EngineScriptDashboard {
1211 // Initialize modules
1312 this . api = new DashboardAPI ( ) ;
1413 this . state = new DashboardState ( ) ;
15- this . charts = new DashboardCharts ( ) ;
1614 this . utils = new DashboardUtils ( ) ;
1715 this . externalServices = null ; // Lazy loaded when needed
1816
@@ -212,15 +210,81 @@ class EngineScriptDashboard {
212210 toggleMobileMenu ( ) {
213211 const sidebar = document . querySelector ( ".sidebar" ) ;
214212 if ( sidebar ) {
213+ const isOpening = ! sidebar . classList . contains ( "mobile-open" ) ;
215214 sidebar . classList . toggle ( "mobile-open" ) ;
215+
216+ // Manage focus when menu opens/closes
217+ if ( isOpening ) {
218+ this . setupMobileFocusTrap ( sidebar ) ;
219+ } else {
220+ this . removeMobileFocusTrap ( ) ;
221+ // Return focus to toggle button
222+ const mobileToggle = document . getElementById ( "mobile-menu-toggle" ) ;
223+ if ( mobileToggle ) mobileToggle . focus ( ) ;
224+ }
216225 }
217226 }
218227
219228 closeMobileMenu ( ) {
220229 const sidebar = document . querySelector ( ".sidebar" ) ;
221230 if ( sidebar ) {
222231 sidebar . classList . remove ( "mobile-open" ) ;
232+ this . removeMobileFocusTrap ( ) ;
233+ // Return focus to toggle button
234+ const mobileToggle = document . getElementById ( "mobile-menu-toggle" ) ;
235+ if ( mobileToggle ) mobileToggle . focus ( ) ;
236+ }
237+ }
238+
239+ /**
240+ * Set up focus trap for mobile menu accessibility
241+ * Prevents focus from escaping the menu overlay when open
242+ */
243+ setupMobileFocusTrap ( sidebar ) {
244+ // Get all focusable elements in sidebar
245+ const focusableSelector = 'a[href], button, [tabindex]:not([tabindex="-1"])' ;
246+ const focusableElements = sidebar . querySelectorAll ( focusableSelector ) ;
247+
248+ if ( focusableElements . length === 0 ) return ;
249+
250+ this . firstFocusable = focusableElements [ 0 ] ;
251+ this . lastFocusable = focusableElements [ focusableElements . length - 1 ] ;
252+
253+ // Focus first element when menu opens
254+ this . firstFocusable . focus ( ) ;
255+
256+ // Store bound handler for removal
257+ this . focusTrapHandler = ( e ) => {
258+ if ( e . key !== 'Tab' ) return ;
259+
260+ if ( e . shiftKey ) {
261+ // Shift+Tab: if on first element, wrap to last
262+ if ( document . activeElement === this . firstFocusable ) {
263+ e . preventDefault ( ) ;
264+ this . lastFocusable . focus ( ) ;
265+ }
266+ } else {
267+ // Tab: if on last element, wrap to first
268+ if ( document . activeElement === this . lastFocusable ) {
269+ e . preventDefault ( ) ;
270+ this . firstFocusable . focus ( ) ;
271+ }
272+ }
273+ } ;
274+
275+ document . addEventListener ( 'keydown' , this . focusTrapHandler ) ;
276+ }
277+
278+ /**
279+ * Remove focus trap when mobile menu closes
280+ */
281+ removeMobileFocusTrap ( ) {
282+ if ( this . focusTrapHandler ) {
283+ document . removeEventListener ( 'keydown' , this . focusTrapHandler ) ;
284+ this . focusTrapHandler = null ;
223285 }
286+ this . firstFocusable = null ;
287+ this . lastFocusable = null ;
224288 }
225289
226290 setupKeyboardShortcuts ( ) {
0 commit comments