@@ -4,16 +4,57 @@ import {attr, controller} from '@github/catalyst'
44export class AvatarFallbackElement extends HTMLElement {
55 @attr uniqueId = ''
66 @attr altText = ''
7+ @attr fallbackSrc = ''
8+
9+ private img : HTMLImageElement | null = null
10+ private boundErrorHandler ?: ( ) => void
711
812 connectedCallback ( ) {
13+ this . img = this . querySelector < HTMLImageElement > ( 'img' ) ?? null
14+ if ( ! this . img ) return
15+
16+ this . boundErrorHandler = ( ) => this . handleImageError ( this . img ! )
17+
18+ // Handle image load errors (404, network failure, etc.)
19+ this . img . addEventListener ( 'error' , this . boundErrorHandler )
20+
21+ // Check if image already failed (error event fired before listener attached)
22+ if ( this . isImageBroken ( this . img ) ) {
23+ this . handleImageError ( this . img )
24+ } else if ( this . isFallbackImage ( this . img ) ) {
25+ this . applyColor ( this . img )
26+ }
27+ }
28+
29+ disconnectedCallback ( ) {
30+ if ( this . boundErrorHandler && this . img ) {
31+ this . img . removeEventListener ( 'error' , this . boundErrorHandler )
32+ }
33+ this . boundErrorHandler = undefined
34+ this . img = null
35+ }
36+
37+ private isImageBroken ( img : HTMLImageElement ) : boolean {
38+ // Image is broken if loading completed but no actual image data loaded
39+ // Skip check for data URIs (fallback SVGs) as they're always valid
40+ return img . complete && img . naturalWidth === 0 && ! img . src . startsWith ( 'data:' )
41+ }
42+
43+ private handleImageError ( img : HTMLImageElement ) {
44+ // Prevent infinite loop if fallback also fails
45+ if ( this . isFallbackImage ( img ) ) return
46+
47+ if ( this . fallbackSrc ) {
48+ img . src = this . fallbackSrc
49+ this . applyColor ( img )
50+ }
51+ }
52+
53+ private applyColor ( img : HTMLImageElement ) {
954 // If either uniqueId or altText is missing, skip color customization so the SVG
1055 // keeps its default gray fill defined in the source and no color override is applied.
1156 if ( ! this . uniqueId || ! this . altText ) return
1257
13- const img = this . querySelector < HTMLImageElement > ( 'img[src^="data:image/svg+xml"]' )
14- if ( ! img ) return
15-
16- // Generate consistent color based on uniqueId and altText (hash must match OP Core)
1758 const text = `${ this . uniqueId } ${ this . altText } `
1859 const hue = this . valueHash ( text )
1960 const color = `hsl(${ hue } , 50%, 30%)`
@@ -46,4 +87,8 @@ export class AvatarFallbackElement extends HTMLElement {
4687 // to avoid breaking the component.
4788 }
4889 }
90+
91+ private isFallbackImage ( img : HTMLImageElement ) : boolean {
92+ return img . src === this . fallbackSrc
93+ }
4994}
0 commit comments