@@ -131,10 +131,25 @@ module.exports = {
131131 }
132132 }
133133
134+ // Track elements that create new landmark scopes (dialog, popover)
135+ const newScopeElements = [ ] ;
136+
134137 return {
135138 GlimmerElementNode ( node ) {
136139 elementStack . push ( node . tag ) ;
137140
141+ // <dialog> and elements with popover attribute create isolated landmark scopes
142+ if ( isNewScopeElement ( node ) ) {
143+ landmarksStack . push ( new Map ( ) ) ;
144+ newScopeElements . push ( node ) ;
145+ }
146+
147+ // Dynamic role values — skip (can't determine role statically)
148+ const roleAttr = node . attributes ?. find ( ( attr ) => attr . name === 'role' ) ;
149+ if ( roleAttr && roleAttr . value ?. type !== 'GlimmerTextNode' ) {
150+ return ;
151+ }
152+
138153 const landmarkRole = getLandmarkRole (
139154 node ,
140155 LANDMARK_ROLES ,
@@ -150,41 +165,22 @@ module.exports = {
150165 }
151166 } ,
152167
153- 'GlimmerElementNode:exit' ( ) {
168+ 'GlimmerElementNode:exit' ( node ) {
154169 elementStack . pop ( ) ;
155- } ,
156-
157- GlimmerBlock ( node ) {
158- const parent = node . parent ;
159- if ( parent && isIfUnless ( parent ) ) {
160- // Entering a branch of an if/unless block.
161- // Push a copy of the scope from BEFORE the conditional
162- // (which is the second-to-last on the stack, since the
163- // conditional's own scope was pushed on enter).
164- const parentScope = landmarksStack . at ( - 2 ) || landmarksStack . at ( - 1 ) ;
165- landmarksStack . push ( cloneLandmarks ( parentScope ) ) ;
166- }
167- } ,
168-
169- 'GlimmerBlock:exit' ( node ) {
170- const parent = node . parent ;
171- if ( parent && isIfUnless ( parent ) ) {
170+ if ( newScopeElements . at ( - 1 ) === node ) {
171+ newScopeElements . pop ( ) ;
172172 landmarksStack . pop ( ) ;
173173 }
174174 } ,
175175
176- GlimmerBlockStatement ( node ) {
177- if ( isIfUnless ( node ) ) {
178- // Push a scope for the conditional block itself.
179- // Each branch (GlimmerBlock) will push its own scope on top.
180- landmarksStack . push ( cloneLandmarks ( currentLandmarks ( ) ) ) ;
181- }
176+ // Every Block gets its own scope (matching the original's Block visitor).
177+ // This handles each, let, with, etc. — not just if/unless.
178+ GlimmerBlock ( ) {
179+ landmarksStack . push ( cloneLandmarks ( currentLandmarks ( ) ) ) ;
182180 } ,
183181
184- 'GlimmerBlockStatement:exit' ( node ) {
185- if ( isIfUnless ( node ) ) {
186- landmarksStack . pop ( ) ;
187- }
182+ 'GlimmerBlock:exit' ( ) {
183+ landmarksStack . pop ( ) ;
188184 } ,
189185
190186 'Program:exit' ( ) {
@@ -194,13 +190,6 @@ module.exports = {
194190 } ,
195191} ;
196192
197- function isIfUnless ( node ) {
198- if ( node . type === 'GlimmerBlockStatement' && node . path ?. type === 'GlimmerPathExpression' ) {
199- return [ 'if' , 'unless' ] . includes ( node . path . original ) ;
200- }
201- return false ;
202- }
203-
204193function getLabel ( node ) {
205194 // Check aria-label
206195 const ariaLabel = node . attributes ?. find ( ( attr ) => attr . name === 'aria-label' ) ;
@@ -232,6 +221,10 @@ function getRoleValue(node) {
232221 return null ;
233222}
234223
224+ function isNewScopeElement ( node ) {
225+ return node . tag === 'dialog' || node . attributes ?. some ( ( attr ) => attr . name === 'popover' ) ;
226+ }
227+
235228function getLandmarkRole ( node , LANDMARK_ROLES , ELEMENT_TO_ROLE , insideSectioning ) {
236229 const role = getRoleValue ( node ) ;
237230 if ( role && LANDMARK_ROLES . has ( role ) ) {
0 commit comments