@@ -510,26 +510,32 @@ function allowsNameFromContent(role: string, targetDescendant: boolean) {
510510 return alwaysAllowsNameFromContent || descendantAllowsNameFromContent ;
511511}
512512
513- function computeAccessibleNameComposite ( element : Element , includeHidden : boolean , collectElements : boolean ) : CompositeString {
513+ export type AccessibleName = CompositeString & {
514+ derivedFromContent : boolean ,
515+ } ;
516+
517+ function computeAccessibleNameComposite ( element : Element , includeHidden : boolean , collectElements : boolean ) : AccessibleName {
514518 // https://w3c.github.io/accname/#computation-steps
515519
516520 // step 1.
517521 // https://w3c.github.io/aria/#namefromprohibited
518522 const elementProhibitsNaming = [ 'caption' , 'code' , 'definition' , 'deletion' , 'emphasis' , 'generic' , 'insertion' , 'mark' , 'paragraph' , 'presentation' , 'strong' , 'subscript' , 'suggestion' , 'superscript' , 'term' , 'time' ] . includes ( getAriaRole ( element ) || '' ) ;
519523 if ( elementProhibitsNaming )
520- return emptyCompositeString ( ) ;
524+ return { ... emptyCompositeString ( ) , derivedFromContent : false } ;
521525
522526 // step 2.
527+ const outDerivedFromContent = { value : false } ;
523528 const result = getTextAlternativeInternal ( element , {
524529 includeHidden,
525530 collectElements,
531+ outDerivedFromContent,
526532 visitedElements : new Set ( ) ,
527533 embeddedInTargetElement : 'self' ,
528534 } ) ;
529- return { text : asFlatString ( result . text ) , elements : result . elements } ;
535+ return { text : asFlatString ( result . text ) , elements : result . elements , derivedFromContent : outDerivedFromContent . value } ;
530536}
531537
532- export function getElementAccessibleName ( element : Element , includeHidden : boolean ) : CompositeString {
538+ export function getElementAccessibleName ( element : Element , includeHidden : boolean ) : AccessibleName {
533539 const cache = ( includeHidden ? cacheAccessibleNameHidden : cacheAccessibleName ) ;
534540 let accessibleName = cache ?. get ( element ) ;
535541 if ( accessibleName === undefined ) {
@@ -553,31 +559,37 @@ export function getElementAccessibleNameText(element: Element, includeHidden: bo
553559 return text ;
554560}
555561
556- export function getElementAccessibleDescription ( element : Element , includeHidden : boolean ) : string {
562+ export type AccessibleDescription = {
563+ text : string ,
564+ derivedFromContent : boolean ,
565+ } ;
566+
567+ export function getElementAccessibleDescription ( element : Element , includeHidden : boolean ) : AccessibleDescription {
557568 const cache = ( includeHidden ? cacheAccessibleDescriptionHidden : cacheAccessibleDescription ) ;
558569 let accessibleDescription = cache ?. get ( element ) ;
559570
560571 if ( accessibleDescription === undefined ) {
561572 // https://w3c.github.io/accname/#mapping_additional_nd_description
562573 // https://www.w3.org/TR/html-aam-1.0/#accdesc-computation
563- accessibleDescription = '' ;
574+ accessibleDescription = { text : '' , derivedFromContent : false } ;
564575
565576 if ( element . hasAttribute ( 'aria-describedby' ) ) {
566577 // precedence 1
567578 const describedBy = getIdRefs ( element , element . getAttribute ( 'aria-describedby' ) ) ;
568- accessibleDescription = asFlatString ( describedBy . map ( ref => getTextAlternativeInternal ( ref , {
579+ accessibleDescription . text = asFlatString ( describedBy . map ( ref => getTextAlternativeInternal ( ref , {
569580 includeHidden,
570581 visitedElements : new Set ( ) ,
571582 embeddedInDescribedBy : { element : ref , hidden : isElementHiddenForAria ( ref ) } ,
572583 } ) . text ) . join ( ' ' ) ) ;
584+ accessibleDescription . derivedFromContent = describedBy . some ( ref => ref === element || element . contains ( ref ) ) ;
573585 } else if ( element . hasAttribute ( 'aria-description' ) ) {
574586 // precedence 2
575- accessibleDescription = asFlatString ( element . getAttribute ( 'aria-description' ) || '' ) ;
587+ accessibleDescription . text = asFlatString ( element . getAttribute ( 'aria-description' ) || '' ) ;
576588 } else {
577589 // TODO: handle precedence 3 - html-aam-specific cases like table>caption.
578590 // https://www.w3.org/TR/html-aam-1.0/#accdesc-computation
579591 // precedence 4
580- accessibleDescription = asFlatString ( element . getAttribute ( 'title' ) || '' ) ;
592+ accessibleDescription . text = asFlatString ( element . getAttribute ( 'title' ) || '' ) ;
581593 }
582594
583595 cache ?. set ( element , accessibleDescription ) ;
@@ -658,13 +670,20 @@ type AccessibleNameOptions = {
658670 visitedElements : Set < Element > ,
659671 collectElements ?: boolean ,
660672 includeHidden ?: boolean ,
673+ // Set to true during the computation when the name is derived from the content of the target
674+ // element, e.g. inner text or aria-labelledby pointing inside the element.
675+ outDerivedFromContent ?: { value : boolean } ,
661676 embeddedInDescribedBy ?: { element : Element , hidden : boolean } ,
662677 embeddedInLabelledBy ?: { element : Element , hidden : boolean } ,
663678 embeddedInLabel ?: { element : Element , hidden : boolean } ,
664679 embeddedInNativeTextAlternative ?: { element : Element , hidden : boolean } ,
665680 embeddedInTargetElement ?: 'self' | 'descendant' ,
666681} ;
667682
683+ function insideTargetElement ( options : AccessibleNameOptions ) {
684+ return options . embeddedInTargetElement === 'self' || options . embeddedInTargetElement === 'descendant' ;
685+ }
686+
668687function getTextAlternativeInternal ( element : Element , options : AccessibleNameOptions ) : CompositeString {
669688 if ( options . visitedElements . has ( element ) )
670689 return emptyCompositeString ( ) ;
@@ -705,8 +724,11 @@ function getTextAlternativeInternal(element: Element, options: AccessibleNameOpt
705724 embeddedInLabel : undefined ,
706725 embeddedInNativeTextAlternative : undefined ,
707726 } ) ) , ' ' , options . collectElements ) ;
708- if ( accessibleName . text )
727+ if ( accessibleName . text ) {
728+ if ( options . outDerivedFromContent && insideTargetElement ( options ) && ( labelledBy || [ ] ) . some ( ref => ref === element || element . contains ( ref ) ) )
729+ options . outDerivedFromContent . value = true ;
709730 return accessibleName ;
731+ }
710732 }
711733
712734 const role = getAriaRole ( element ) || '' ;
@@ -972,6 +994,8 @@ function getTextAlternativeInternal(element: Element, options: AccessibleNameOpt
972994 // So we follow the spec everywhere except for the target element itself. This can probably be improved.
973995 const maybeTrimmedAccessibleName = options . embeddedInTargetElement === 'self' ? trimFlatString ( accessibleName . text ) : accessibleName . text ;
974996 if ( maybeTrimmedAccessibleName ) {
997+ if ( options . outDerivedFromContent && insideTargetElement ( options ) && trimFlatString ( accessibleName . text ) )
998+ options . outDerivedFromContent . value = true ;
975999 // This element owns the accumulated content - record it alongside the descendants it was computed from.
9761000 accessibleName . elements ?. add ( element ) ;
9771001 return accessibleName ;
@@ -1242,12 +1266,12 @@ export function receivesPointerEvents(element: Element): boolean {
12421266 return result ;
12431267}
12441268
1245- let cacheAccessibleName : Map < Element , CompositeString > | undefined ;
1246- let cacheAccessibleNameHidden : Map < Element , CompositeString > | undefined ;
1269+ let cacheAccessibleName : Map < Element , AccessibleName > | undefined ;
1270+ let cacheAccessibleNameHidden : Map < Element , AccessibleName > | undefined ;
12471271let cacheAccessibleNameText : Map < Element , string > | undefined ;
12481272let cacheAccessibleNameTextHidden : Map < Element , string > | undefined ;
1249- let cacheAccessibleDescription : Map < Element , string > | undefined ;
1250- let cacheAccessibleDescriptionHidden : Map < Element , string > | undefined ;
1273+ let cacheAccessibleDescription : Map < Element , AccessibleDescription > | undefined ;
1274+ let cacheAccessibleDescriptionHidden : Map < Element , AccessibleDescription > | undefined ;
12511275let cacheAccessibleErrorMessage : Map < Element , string > | undefined ;
12521276let cacheIsHidden : Map < Element , boolean > | undefined ;
12531277let cachePseudoContent : Map < Element , string | undefined > | undefined ;
0 commit comments