@@ -39,6 +39,66 @@ interface AccessibilityContext {
3939 visitedNodes : Set < Node > ;
4040}
4141
42+ /**
43+ * Returns the child nodes to traverse for building the flattened
44+ * accessibility tree, handling shadow DOM and slot projection:
45+ *
46+ * 1. If the node has an open shadow root → return shadow root's children
47+ * 2. If the node is a <slot> → return assigned nodes (or default content)
48+ * 3. Otherwise → return the node's direct children
49+ */
50+ function getAccessibleChildNodes ( node : Node ) : Node [ ] {
51+ // Shadow host: traverse into the shadow tree
52+ if ( isElement ( node ) && node . shadowRoot ) {
53+ return Array . from ( node . shadowRoot . childNodes ) ;
54+ }
55+
56+ // Slot element: traverse assigned (projected) content, or default content
57+ if ( isElement ( node ) && node . localName === "slot" ) {
58+ const slot = node as HTMLSlotElement ;
59+ const assigned = slot . assignedNodes ( { flatten : true } ) ;
60+
61+ if ( assigned . length > 0 ) {
62+ return assigned ;
63+ }
64+
65+ // No assigned content — fall through to default slot content (childNodes)
66+ }
67+
68+ return Array . from ( node . childNodes ) ;
69+ }
70+
71+ /**
72+ * Shadow-aware querySelectorAll: searches the node and all descendant
73+ * shadow roots for elements matching the selector.
74+ */
75+ function deepQuerySelectorAll (
76+ node : Node ,
77+ selector : string
78+ ) : Element [ ] {
79+ if ( ! isElement ( node ) ) {
80+ return [ ] ;
81+ }
82+
83+ const results : Element [ ] = Array . from ( node . querySelectorAll ( selector ) ) ;
84+
85+ // Also search inside shadow roots
86+ const searchShadowRoots = ( root : Element ) => {
87+ if ( root . shadowRoot ) {
88+ results . push (
89+ ...Array . from ( root . shadowRoot . querySelectorAll ( selector ) )
90+ ) ;
91+ root . shadowRoot . querySelectorAll ( "*" ) . forEach ( searchShadowRoots ) ;
92+ }
93+ } ;
94+
95+ // Search the node itself and all its descendants
96+ searchShadowRoots ( node ) ;
97+ node . querySelectorAll ( "*" ) . forEach ( searchShadowRoots ) ;
98+
99+ return results ;
100+ }
101+
42102function addAlternateReadingOrderNodes (
43103 node : Element ,
44104 alternateReadingOrderMap : Map < Node , Set < Node > > ,
@@ -72,11 +132,9 @@ function mapAlternateReadingOrder(node: Node) {
72132 return alternateReadingOrderMap ;
73133 }
74134
75- node
76- . querySelectorAll ( "[aria-flowto]" )
77- . forEach ( ( parentNode ) =>
78- addAlternateReadingOrderNodes ( parentNode , alternateReadingOrderMap , node )
79- ) ;
135+ deepQuerySelectorAll ( node , "[aria-flowto]" ) . forEach ( ( parentNode ) =>
136+ addAlternateReadingOrderNodes ( parentNode , alternateReadingOrderMap , node )
137+ ) ;
80138
81139 return alternateReadingOrderMap ;
82140}
@@ -107,9 +165,9 @@ function getAllOwnedNodes(node: Node) {
107165 return ownedNodes ;
108166 }
109167
110- node
111- . querySelectorAll ( "[aria-owns]" )
112- . forEach ( ( owningNode ) => addOwnedNodes ( owningNode , ownedNodes , node ) ) ;
168+ deepQuerySelectorAll ( node , "[aria-owns]" ) . forEach ( ( owningNode ) =>
169+ addOwnedNodes ( owningNode , ownedNodes , node )
170+ ) ;
113171
114172 return ownedNodes ;
115173}
@@ -160,7 +218,23 @@ function growTree(
160218 tree . parentDialog = parentDialog ;
161219 }
162220
163- node . childNodes . forEach ( ( childNode ) => {
221+ /**
222+ * Determine which child nodes to traverse based on the flattened tree:
223+ *
224+ * - If the node has an open shadow root, traverse the shadow tree instead
225+ * of the light DOM children (shadow DOM replaces light DOM in the
226+ * accessibility tree).
227+ * - If a child is a <slot>, traverse its assigned nodes (the projected
228+ * light DOM content). If no nodes are assigned, fall back to the slot's
229+ * default content (its own childNodes).
230+ * - Otherwise, traverse the node's direct childNodes (standard light DOM).
231+ *
232+ * REF: https://www.w3.org/TR/wai-aria-1.2/#accessibility_tree
233+ * REF: https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement/assignedNodes
234+ */
235+ const childNodes = getAccessibleChildNodes ( node ) ;
236+
237+ childNodes . forEach ( ( childNode ) => {
164238 if ( isHiddenFromAccessibilityTree ( childNode ) ) {
165239 return ;
166240 }
0 commit comments