11import { Controller } from "@hotwired/stimulus"
22import { announce , clearAnnounceTimeout } from "layered_ui/utilities/announce"
33import { isMobile } from "layered_ui/utilities/layout"
4+ import { lockBodyScroll , unlockBodyScroll } from "layered_ui/utilities/scroll_lock"
45
56export default class extends Controller {
67 static targets = [ "navigation" , "backdrop" , "toggleButton" , "openIcon" , "closeIcon" ]
78
89 connect ( ) {
910 this . previousActiveElement = null
1011 this . isOpen = false
12+ this . isScrollLocked = false
1113 this . _resizeFrame = null
1214 this . boundHandleResize = ( ) => {
1315 if ( this . _resizeFrame ) return
@@ -54,6 +56,7 @@ export default class extends Controller {
5456 this . navigationTarget . classList . add ( "open" )
5557 this . backdropTarget . classList . add ( "open" )
5658 this . setNavigationInteractivity ( true )
59+ this . updateScrollLock ( )
5760
5861 // Update ARIA attributes and swap icons
5962 if ( this . hasToggleButtonTarget ) {
@@ -89,6 +92,7 @@ export default class extends Controller {
8992 this . navigationTarget . classList . remove ( "open" )
9093 this . backdropTarget . classList . remove ( "open" )
9194 this . setNavigationInteractivity ( false )
95+ this . unlockScroll ( )
9296
9397 // Update ARIA attributes and swap icons
9498 if ( this . hasToggleButtonTarget ) {
@@ -116,6 +120,7 @@ export default class extends Controller {
116120 clearAnnounceTimeout ( this )
117121 cancelAnimationFrame ( this . _resizeFrame )
118122 window . removeEventListener ( "resize" , this . boundHandleResize )
123+ this . unlockScroll ( )
119124 this . previousActiveElement = null
120125 }
121126
@@ -125,6 +130,7 @@ export default class extends Controller {
125130 // In overlay mode (default), always respect isOpen state regardless of viewport
126131 if ( isMobile ( ) || ! this . alwaysShow ) {
127132 this . setNavigationInteractivity ( this . isOpen )
133+ this . updateScrollLock ( )
128134 return
129135 }
130136
@@ -133,6 +139,7 @@ export default class extends Controller {
133139 this . navigationTarget . classList . remove ( "open" )
134140 this . backdropTarget . classList . remove ( "open" )
135141 this . setNavigationInteractivity ( true )
142+ this . unlockScroll ( )
136143
137144 if ( this . hasToggleButtonTarget ) {
138145 this . toggleButtonTarget . setAttribute ( "aria-expanded" , "false" )
@@ -157,6 +164,28 @@ export default class extends Controller {
157164 this . navigationTarget . removeAttribute ( "aria-hidden" )
158165 }
159166
167+ updateScrollLock ( ) {
168+ if ( this . isOpen && isMobile ( ) ) {
169+ this . lockScroll ( )
170+ } else {
171+ this . unlockScroll ( )
172+ }
173+ }
174+
175+ lockScroll ( ) {
176+ if ( this . isScrollLocked ) return
177+
178+ lockBodyScroll ( )
179+ this . isScrollLocked = true
180+ }
181+
182+ unlockScroll ( ) {
183+ if ( ! this . isScrollLocked ) return
184+
185+ unlockBodyScroll ( )
186+ this . isScrollLocked = false
187+ }
188+
160189 get alwaysShow ( ) {
161190 return this . element . classList . contains ( "l-ui-body--always-show-navigation" )
162191 }
0 commit comments