@openproject/primer-view-components 0.79.0 → 0.79.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,15 @@
1
1
  export declare class AvatarFallbackElement extends HTMLElement {
2
2
  uniqueId: string;
3
3
  altText: string;
4
+ fallbackSrc: string;
5
+ private img;
6
+ private boundErrorHandler?;
4
7
  connectedCallback(): void;
8
+ disconnectedCallback(): void;
9
+ private isImageBroken;
10
+ private handleImageError;
11
+ private applyColor;
5
12
  private valueHash;
6
13
  private updateSvgColor;
14
+ private isFallbackImage;
7
15
  }
@@ -10,16 +10,50 @@ let AvatarFallbackElement = class AvatarFallbackElement extends HTMLElement {
10
10
  super(...arguments);
11
11
  this.uniqueId = '';
12
12
  this.altText = '';
13
+ this.fallbackSrc = '';
14
+ this.img = null;
13
15
  }
14
16
  connectedCallback() {
17
+ this.img = this.querySelector('img') ?? null;
18
+ if (!this.img)
19
+ return;
20
+ this.boundErrorHandler = () => this.handleImageError(this.img);
21
+ // Handle image load errors (404, network failure, etc.)
22
+ this.img.addEventListener('error', this.boundErrorHandler);
23
+ // Check if image already failed (error event fired before listener attached)
24
+ if (this.isImageBroken(this.img)) {
25
+ this.handleImageError(this.img);
26
+ }
27
+ else if (this.isFallbackImage(this.img)) {
28
+ this.applyColor(this.img);
29
+ }
30
+ }
31
+ disconnectedCallback() {
32
+ if (this.boundErrorHandler && this.img) {
33
+ this.img.removeEventListener('error', this.boundErrorHandler);
34
+ }
35
+ this.boundErrorHandler = undefined;
36
+ this.img = null;
37
+ }
38
+ isImageBroken(img) {
39
+ // Image is broken if loading completed but no actual image data loaded
40
+ // Skip check for data URIs (fallback SVGs) as they're always valid
41
+ return img.complete && img.naturalWidth === 0 && !img.src.startsWith('data:');
42
+ }
43
+ handleImageError(img) {
44
+ // Prevent infinite loop if fallback also fails
45
+ if (this.isFallbackImage(img))
46
+ return;
47
+ if (this.fallbackSrc) {
48
+ img.src = this.fallbackSrc;
49
+ this.applyColor(img);
50
+ }
51
+ }
52
+ applyColor(img) {
15
53
  // If either uniqueId or altText is missing, skip color customization so the SVG
16
54
  // keeps its default gray fill defined in the source and no color override is applied.
17
55
  if (!this.uniqueId || !this.altText)
18
56
  return;
19
- const img = this.querySelector('img[src^="data:image/svg+xml"]');
20
- if (!img)
21
- return;
22
- // Generate consistent color based on uniqueId and altText (hash must match OP Core)
23
57
  const text = `${this.uniqueId}${this.altText}`;
24
58
  const hue = this.valueHash(text);
25
59
  const color = `hsl(${hue}, 50%, 30%)`;
@@ -49,6 +83,9 @@ let AvatarFallbackElement = class AvatarFallbackElement extends HTMLElement {
49
83
  // to avoid breaking the component.
50
84
  }
51
85
  }
86
+ isFallbackImage(img) {
87
+ return img.src === this.fallbackSrc;
88
+ }
52
89
  };
53
90
  __decorate([
54
91
  attr
@@ -56,6 +93,9 @@ __decorate([
56
93
  __decorate([
57
94
  attr
58
95
  ], AvatarFallbackElement.prototype, "altText", void 0);
96
+ __decorate([
97
+ attr
98
+ ], AvatarFallbackElement.prototype, "fallbackSrc", void 0);
59
99
  AvatarFallbackElement = __decorate([
60
100
  controller
61
101
  ], AvatarFallbackElement);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openproject/primer-view-components",
3
- "version": "0.79.0",
3
+ "version": "0.79.1",
4
4
  "description": "ViewComponents of the Primer Design System for OpenProject",
5
5
  "main": "app/assets/javascripts/primer_view_components.js",
6
6
  "module": "app/components/primer/primer.js",
@@ -5775,7 +5775,7 @@
5775
5775
  "name": "src",
5776
5776
  "type": "String",
5777
5777
  "default": "`nil`",
5778
- "description": "The source url of the avatar image. When nil, renders a fallback with initials."
5778
+ "description": "The source url of the avatar image. When nil or a broken URL, it renders a fallback with initials."
5779
5779
  },
5780
5780
  {
5781
5781
  "name": "alt",
@@ -18882,7 +18882,7 @@
18882
18882
  "name": "src",
18883
18883
  "type": "String",
18884
18884
  "default": "`nil`",
18885
- "description": "The source url of the avatar image. When nil, renders a fallback with initials."
18885
+ "description": "The source url of the avatar image. When nil or a broken URL, it renders a fallback with initials."
18886
18886
  },
18887
18887
  {
18888
18888
  "name": "alt",
@@ -19040,6 +19040,32 @@
19040
19040
  "color-contrast"
19041
19041
  ]
19042
19042
  }
19043
+ },
19044
+ {
19045
+ "preview_path": "primer/open_project/avatar_with_fallback/broken_image_404",
19046
+ "name": "broken_image_404",
19047
+ "snapshot": "true",
19048
+ "skip_rules": {
19049
+ "wont_fix": [
19050
+ "region"
19051
+ ],
19052
+ "will_fix": [
19053
+ "color-contrast"
19054
+ ]
19055
+ }
19056
+ },
19057
+ {
19058
+ "preview_path": "primer/open_project/avatar_with_fallback/multiple_broken_images",
19059
+ "name": "multiple_broken_images",
19060
+ "snapshot": "false",
19061
+ "skip_rules": {
19062
+ "wont_fix": [
19063
+ "region"
19064
+ ],
19065
+ "will_fix": [
19066
+ "color-contrast"
19067
+ ]
19068
+ }
19043
19069
  }
19044
19070
  ],
19045
19071
  "subcomponents": []
@@ -1633,6 +1633,32 @@
1633
1633
  "color-contrast"
1634
1634
  ]
1635
1635
  }
1636
+ },
1637
+ {
1638
+ "preview_path": "primer/open_project/avatar_with_fallback/broken_image_404",
1639
+ "name": "broken_image_404",
1640
+ "snapshot": "true",
1641
+ "skip_rules": {
1642
+ "wont_fix": [
1643
+ "region"
1644
+ ],
1645
+ "will_fix": [
1646
+ "color-contrast"
1647
+ ]
1648
+ }
1649
+ },
1650
+ {
1651
+ "preview_path": "primer/open_project/avatar_with_fallback/multiple_broken_images",
1652
+ "name": "multiple_broken_images",
1653
+ "snapshot": "false",
1654
+ "skip_rules": {
1655
+ "wont_fix": [
1656
+ "region"
1657
+ ],
1658
+ "will_fix": [
1659
+ "color-contrast"
1660
+ ]
1661
+ }
1636
1662
  }
1637
1663
  ]
1638
1664
  },