22 isServer ,
33 type ReactiveController ,
44 type ReactiveControllerHost ,
5- type LitElement ,
65} from 'lit' ;
76
87function isARIAMixinProp ( key : string ) : key is keyof ARIAMixin {
@@ -59,17 +58,19 @@ function aria(
5958 protos . get ( target ) . add ( key ) ;
6059}
6160
62- function getLabelText ( label : HTMLElement ) {
63- if ( label . hidden ) {
61+ function getLabelText ( label : Node ) {
62+ if ( ! ( label instanceof HTMLElement ) || label . hidden ) {
6463 return '' ;
6564 } else {
6665 const ariaLabel = label . getAttribute ?.( 'aria-label' ) ;
6766 return ariaLabel ?? label . textContent ;
6867 }
6968}
7069
70+ type InternalsHost = ReactiveControllerHost & HTMLElement ;
71+
7172export class InternalsController implements ReactiveController , ARIAMixin {
72- private static instances = new WeakMap < ReactiveControllerHost , InternalsController > ( ) ;
73+ private static instances = new WeakMap < HTMLElement , InternalsController > ( ) ;
7374
7475 declare readonly form : ElementInternals [ 'form' ] ;
7576 declare readonly shadowRoot : ElementInternals [ 'shadowRoot' ] ;
@@ -79,7 +80,7 @@ export class InternalsController implements ReactiveController, ARIAMixin {
7980 declare readonly willValidate : ElementInternals [ 'willValidate' ] ;
8081 declare readonly validationMessage : ElementInternals [ 'validationMessage' ] ;
8182
82- public static getLabels ( host : ReactiveControllerHost ) : Element [ ] {
83+ public static getLabels ( host : InternalsHost ) : Element [ ] {
8384 return Array . from ( this . instances . get ( host ) ?. internals . labels ?? [ ] ) as Element [ ] ;
8485 }
8586
@@ -89,8 +90,8 @@ export class InternalsController implements ReactiveController, ARIAMixin {
8990 * @param host - The listbox item element (option or option-like).
9091 * @param value - Position in set (1-based), or null to clear.
9192 */
92- public static setAriaPosInSet ( host : Element , value : number | string | null ) : void {
93- const instance = this . instances . get ( host as unknown as ReactiveControllerHost ) ;
93+ public static setAriaPosInSet ( host : HTMLElement , value : number | string | null ) : void {
94+ const instance = this . instances . get ( host ) ;
9495 if ( instance ) {
9596 instance . ariaPosInSet = value != null ? String ( value ) : null ;
9697 } else if ( value != null ) {
@@ -106,8 +107,8 @@ export class InternalsController implements ReactiveController, ARIAMixin {
106107 * @param host - The listbox item element (option or option-like).
107108 * @param value - Total set size, or null to clear.
108109 */
109- public static setAriaSetSize ( host : Element , value : number | string | null ) : void {
110- const instance = this . instances . get ( host as unknown as ReactiveControllerHost ) ;
110+ public static setAriaSetSize ( host : HTMLElement , value : number | string | null ) : void {
111+ const instance = this . instances . get ( host ) ;
111112 if ( instance ) {
112113 instance . ariaSetSize = value != null ? String ( value ) : null ;
113114 } else if ( value != null ) {
@@ -121,8 +122,8 @@ export class InternalsController implements ReactiveController, ARIAMixin {
121122 * Gets aria-posinset from a listbox item (internals or attribute).
122123 * @param host - The listbox item element.
123124 */
124- public static getAriaPosInSet ( host : Element ) : string | null {
125- const instance = this . instances . get ( host as unknown as ReactiveControllerHost ) ;
125+ public static getAriaPosInSet ( host : HTMLElement ) : string | null {
126+ const instance = this . instances . get ( host ) ;
126127 return instance != null ?
127128 instance . ariaPosInSet
128129 : host . getAttribute ( 'aria-posinset' ) ;
@@ -132,21 +133,17 @@ export class InternalsController implements ReactiveController, ARIAMixin {
132133 * Gets aria-setsize from a listbox item (internals or attribute).
133134 * @param host - The listbox item element.
134135 */
135- public static getAriaSetSize ( host : Element ) : string | null {
136- const instance = this . instances . get ( host as unknown as ReactiveControllerHost ) ;
136+ public static getAriaSetSize ( host : HTMLElement ) : string | null {
137+ const instance = this . instances . get ( host ) ;
137138 return instance != null ?
138139 instance . ariaSetSize
139140 : host . getAttribute ( 'aria-setsize' ) ;
140141 }
141142
142-
143143 public static isSafari : boolean =
144144 ! isServer && / ^ ( (? ! c h r o m e | a n d r o i d ) .) * s a f a r i / i. test ( navigator . userAgent ) ;
145145
146- public static of (
147- host : ReactiveControllerHost ,
148- options ?: InternalsControllerOptions ,
149- ) : InternalsController {
146+ public static of ( host : InternalsHost , options ?: InternalsControllerOptions ) : InternalsController {
150147 constructingAllowed = true ;
151148 // implement the singleton pattern
152149 // using a public static constructor method is much easier to manage,
@@ -206,21 +203,16 @@ export class InternalsController implements ReactiveController, ARIAMixin {
206203 @aria ariaValueNow : string | null = null ;
207204 @aria ariaValueText : string | null = null ;
208205
209- /** WARNING: be careful of cross-root ARIA browser support */
206+ /** As of April 2025, the following are considered Baseline supported in evergreen browsers */
210207 @aria ariaActiveDescendantElement : Element | null = null ;
211- /** WARNING: be careful of cross-root ARIA browser support */
212208 @aria ariaControlsElements : Element [ ] | null = null ;
213- /** WARNING: be careful of cross-root ARIA browser support */
214209 @aria ariaDescribedByElements : Element [ ] | null = null ;
215- /** WARNING: be careful of cross-root ARIA browser support */
216210 @aria ariaDetailsElements : Element [ ] | null = null ;
217- /** WARNING: be careful of cross-root ARIA browser support */
218211 @aria ariaErrorMessageElements : Element [ ] | null = null ;
219- /** WARNING: be careful of cross-root ARIA browser support */
220212 @aria ariaFlowToElements : Element [ ] | null = null ;
221- /** WARNING: be careful of cross-root ARIA browser support */
222213 @aria ariaLabelledByElements : Element [ ] | null = null ;
223- /** WARNING: be careful of cross-root ARIA browser support */
214+
215+ /** As of February 2026, this is not supported in Chromium browsers */
224216 @aria ariaOwnsElements : Element [ ] | null = null ;
225217
226218 /** True when the control is disabled via it's containing fieldset element */
@@ -243,16 +235,14 @@ export class InternalsController implements ReactiveController, ARIAMixin {
243235 /** A best-attempt based on observed behaviour in FireFox 115 on fedora 38 */
244236 get computedLabelText ( ) : string {
245237 return this . internals . ariaLabel
246- || Array . from ( this . internals . labels as NodeListOf < HTMLElement > )
238+ || Array . from ( this . internals . labels )
247239 . reduce ( ( acc , label ) =>
248240 `${ acc } ${ getLabelText ( label ) } ` , '' ) ;
249241 }
250242
251243 private get element ( ) {
252244 if ( isServer ) {
253- // FIXME(bennyp): a little white lie, which may break
254- // when the controller is applied to non-lit frameworks.
255- return this . host as LitElement ;
245+ return this . host ;
256246 } else {
257247 return this . host instanceof HTMLElement ? this . host : this . options ?. getHTMLElement ?.( ) ;
258248 }
@@ -262,10 +252,7 @@ export class InternalsController implements ReactiveController, ARIAMixin {
262252
263253 private _formDisabled = false ;
264254
265- private constructor (
266- public host : ReactiveControllerHost ,
267- private options ?: InternalsControllerOptions ,
268- ) {
255+ private constructor ( public host : InternalsHost , private options ?: InternalsControllerOptions ) {
269256 if ( ! constructingAllowed ) {
270257 throw new Error ( 'InternalsController must be constructed with `InternalsController.for()`' ) ;
271258 }
0 commit comments