@stackoverflow/stacks 3.0.0-beta.20 → 3.0.0-beta.22

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.
@@ -5,6 +5,13 @@
5
5
  --_no-ty-offset: 0;
6
6
  --_no-ty: var(--theme-topbar-height, calc(var(--su-static48) + var(--su-static8)));
7
7
  .construct-notice-component(s-banner);
8
+
9
+ &--icon {
10
+ //Remove the icon backgound color
11
+ --_no-icon-bg: transparent;
12
+ margin-right: var(--su8);
13
+ align-self: flex-start;
14
+ }
8
15
 
9
16
  &[aria-hidden="true"] { // If you want to hide and reveal the banner
10
17
  --_no-ty-offset: -1;
@@ -27,18 +34,12 @@
27
34
  padding-top: 93px;
28
35
  }
29
36
 
30
- & &--container { // When we want to keep hero content capped
31
- margin: 0 auto;
32
- max-width: var(--su1024);
33
- position: relative;
34
- width: 100%;
35
- }
36
-
37
- border-width: 0 0 var(--su-static1);
37
+ display: flex;
38
+ align-items: center;
39
+ padding: var(--su16);
38
40
  inset: 0 0 auto 0;
39
- padding: var(--su12);
40
41
  position: fixed;
41
42
  transform: translate3d(0, calc(var(--_no-ty) * var(--_no-ty-offset)), 0);
42
43
  width: 100%;
43
44
  z-index: calc(var(--zi-navigation-fixed) - 1); // Tuck below topbar
44
- }
45
+ }
@@ -6,20 +6,24 @@
6
6
  --_bu-g: var(--su8);
7
7
  --_bu-px: var(--su16);
8
8
  --_bu-py: calc(var(--su8) + var(--su2)); // 10px
9
+ --_bu-badge-fs: var(--fs-caption);
10
+ --_bu-badge-px: var(--su6);
11
+ --_bu-badge-py: var(--su2);
9
12
  --_bu-dropdown-bw: var(--su-static4);
10
13
 
11
14
  // VARIANTS
12
15
  // Base
13
16
  &:not(&__danger):not(&__featured):not(&__tonal):not(&__link):not(&__unset):not(&__facebook):not(&__github):not(&__google) { // Exclude default styles from impacting these variants
14
17
  --_bu-bg: var(--theme-button-color, var(--theme-secondary));
15
- --_bu-bg-disabled: var(--theme-secondary-300);
18
+ --_bu-bg-disabled: var(--black-350);
16
19
  --_bu-bg-hover: var(--theme-button-hover-background-color, var(--theme-secondary-500));
17
20
  --_bu-fc: var(--white);
21
+ --_bu-fc-disabled: var(--black-050);
18
22
  --_bu-fc-hover: var(--theme-button-hover-color, var(--white));
19
- --_bu-badge-bg: var(--theme-secondary-200);
20
- --_bu-badge-fc: var(--theme-secondary-600);
21
- --_bu-badge-bg-disabled: var(--theme-secondary-200);
22
- --_bu-badge-fc-disabled: var(--theme-secondary-300);
23
+ --_bu-badge-bg: var(--theme-secondary-500);
24
+ --_bu-badge-fc: var(--white);
25
+ --_bu-badge-bg-disabled: var(--black-250);
26
+ --_bu-badge-fc-disabled: var(--black-050);
23
27
 
24
28
  &.s-btn__clear {
25
29
  --_bu-bg: transparent;
@@ -29,10 +33,10 @@
29
33
  --_bu-fc: var(--theme-secondary-600);
30
34
  --_bu-fc-disabled: var(--theme-secondary-300);
31
35
  --_bu-fc-hover: var(--_bu-fc);
32
- --_bu-badge-bg: var(--theme-secondary-500);
33
- --_bu-badge-fc: var(--white);
34
- --_bu-badge-bg-disabled: var(--theme-secondary-300);
35
- --_bu-badge-fc-disabled: var(--white);
36
+ --_bu-badge-bg: var(--theme-secondary-100);
37
+ --_bu-badge-fc: var(--theme-secondary-500);
38
+ --_bu-badge-bg-disabled: var(--black-100);
39
+ --_bu-badge-fc-disabled: var(--black-350);
36
40
 
37
41
  .highcontrast-mode({
38
42
  --_bu-bc: var(--theme-secondary-600);
@@ -44,13 +48,14 @@
44
48
  --_bu-bg: var(--red-400);
45
49
  --_bu-fc: var(--white);
46
50
  --_bu-bg-disabled: var(--red-200);
51
+ --_bu-fc-disabled: var(--black-050);
47
52
  --_bu-bg-hover: var(--red-500);
48
53
  --_bu-bg-selected: var(--red-500);
49
54
  --_bu-fc-selected: var(--_bu-fc);
50
- --_bu-badge-bg: var(--red-100);
51
- --_bu-badge-fc: var(--red-400);
52
- --_bu-badge-bg-disabled: var(--white);
53
- --_bu-badge-fc-disabled: var(--red-200);
55
+ --_bu-badge-bg: var(--red-500);
56
+ --_bu-badge-fc: var(--black-050);
57
+ --_bu-badge-bg-disabled: var(--red-300);
58
+ --_bu-badge-fc-disabled: var(--black-100);
54
59
 
55
60
  &.s-btn__clear {
56
61
  --_bu-bg: transparent;
@@ -61,11 +66,10 @@
61
66
  --_bu-fc-disabled: var(--red-200);
62
67
  --_bu-fc-hover: var(--red-500);
63
68
  --_bu-fc-selected: var(--red-500);
64
- --_bu-badge-bg: var(--red-400);
65
- --_bu-badge-bg-selected: var(--red-400);
66
- --_bu-badge-fc: var(--red-100);
67
- --_bu-badge-bg-disabled: var(--red-200);
68
- --_bu-badge-fc-disabled: var(--white);
69
+ --_bu-badge-bg: var(--red-100);
70
+ --_bu-badge-fc: var(--red-500);
71
+ --_bu-badge-bg-disabled: var(--red-100);
72
+ --_bu-badge-fc-disabled: var(--red-300);
69
73
 
70
74
  .highcontrast-mode({
71
75
  --_bu-bc: var(--red-600);
@@ -81,23 +85,24 @@
81
85
  --_bu-bg-selected: var(--purple-500);
82
86
  --_bu-fc: var(--white);
83
87
  --_bu-fc-selected: var(--_bu-fc);
84
- --_bu-badge-bg: var(--purple-100);
85
- --_bu-badge-fc: var(--purple-400);
86
- --_bu-badge-bg-disabled: var(--white);
87
- --_bu-badge-fc-disabled: var(--purple-200);
88
+ --_bu-badge-bg: var(--purple-500);
89
+ --_bu-badge-fc: var(--black-050);
90
+ --_bu-badge-bg-disabled: var(--purple-300);
91
+ --_bu-badge-fc-disabled: var(--black-100);
88
92
  }
89
93
 
90
94
  &&__tonal {
91
95
  --_bu-bg: var(--black-150);
92
96
  --_bu-bg-disabled: var(--black-100);
93
97
  --_bu-bg-hover: var(--black-200);
98
+ --_bu-bg-selected: var(--black-200);
94
99
  --_bu-fc: var(--black);
95
100
  --_bu-fc-disabled: var(--black-300);
96
101
  --_bu-fc-selected: var(--_bu-fc);
97
- --_bu-badge-bg: var(--black-500);
98
- --_bu-badge-fc: var(--white);
99
- --_bu-badge-bg-disabled: var(--black-300);
100
- --_bu-badge-fc-disabled: var(--white);
102
+ --_bu-badge-bg: var(--black-200);
103
+ --_bu-badge-fc: var(--black-600);
104
+ --_bu-badge-bg-disabled: var(--black-100);
105
+ --_bu-badge-fc-disabled: var(--black-350);
101
106
 
102
107
  .highcontrast-mode({
103
108
  --_bu-bc: var(--black-300);
@@ -200,8 +205,9 @@
200
205
  var(--_bu-bg-selected, var(--_bu-bg)),
201
206
  var(--white) var(--_bu-bg-selected-overlay-o)
202
207
  );
203
- --_bu-bg-gradient-top: var(--_bu-bg-selected, var(--_bu-bg));
204
- --_bu-bg-gradient-bottom: var(--_bu-bg-selected-overlay, var(--_bu-bg));
208
+ --_bu-bg-gradient-top: var(--_bu-bg-selected-overlay, var(--_bu-bg));
209
+ --_bu-bg-gradient-bottom: var(--_bu-bg-selected, var(--_bu-bg));
210
+
205
211
 
206
212
  .dark-mode({
207
213
  --_bu-bg-selected-overlay-o: 13%;
@@ -229,15 +235,15 @@
229
235
  }
230
236
 
231
237
  // CHILD ELEMENTS
232
- // TODO SHINE fine-tune badge styles
233
238
  & &--badge {
234
239
  background-color: var(--_bu-badge-bg);
235
240
  border-radius: var(--br-pill);
236
241
  display: inline-block;
237
- font-size: var(--fs-caption);
242
+ font-size: var(--_bu-badge-fs);
238
243
  line-height: inherit;
239
244
  opacity: var(--_bu-badge-o);
240
- padding: var(--su1) var(--su4) 0;
245
+ padding: var(--_bu-badge-py) var(--_bu-badge-px);
246
+ margin-block: calc(var(--_bu-badge-py) * -1);
241
247
  }
242
248
 
243
249
  & &--number {
@@ -264,6 +270,9 @@
264
270
  &&__sm {
265
271
  --_bu-lh: var(--lh-sm);
266
272
  --_bu-px: var(--su12);
273
+ --_bu-badge-fs: var(--fs-fine);
274
+ --_bu-badge-py: var(--su1);
275
+ --_bu-badge-px: calc(var(--su2) + var(--su1)); // 3px
267
276
  }
268
277
  &&__xs {
269
278
  --_bu-g: var(--su4);
@@ -279,6 +288,7 @@
279
288
  --_bu-fs: var(--fs-body2);
280
289
  --_bu-px: var(--su24);
281
290
  --_bu-py: calc(var(--su12) + var(--su1)); // 13px
291
+ --_bu-badge-py: calc(var(--su2) + var(--su1)); // 3px
282
292
  }
283
293
 
284
294
  // INTERACTION
@@ -0,0 +1,88 @@
1
+ .s-loader {
2
+ --_ld-color: var(--black-600);
3
+ --_ld-gap: calc(var(--_ld-size) / 2);
4
+ --_ld-size: calc(var(--su4) + var(--su1)); // 5px
5
+ --_ld-offset: calc(calc(var(--_ld-size) / 8) * -5); // -5/8ths of the size
6
+
7
+ // MODIFIERS
8
+ &__sm {
9
+ --_ld-size: calc(calc(var(--su8) - var(--su1)) / 2); // 3.5px
10
+ margin-left: var(--su1);
11
+ margin-right: var(--su1);
12
+ }
13
+
14
+ &__lg {
15
+ --_ld-size: var(--su8);
16
+ }
17
+
18
+ // CHILD ELEMENTS – three blocks via pseudo-elements (::before, --sr-text::before, ::after)
19
+ &:before,
20
+ & &--sr-text:before,
21
+ &:after {
22
+ background-color: currentColor;
23
+ content: "";
24
+ display: block;
25
+ height: var(--_ld-size);
26
+ width: var(--_ld-size);
27
+
28
+ animation: loader-animation .8s cubic-bezier(1, 1, 0, 1) infinite;
29
+ }
30
+
31
+ & &--sr-text {
32
+ // Visible flex item so its ::before (middle block) shows; overflow visible so translateY isn't clipped
33
+ display: block;
34
+ flex-shrink: 0;
35
+ height: var(--_ld-size);
36
+ width: var(--_ld-size);
37
+ font-size: 0;
38
+ overflow: visible;
39
+ }
40
+
41
+ & &--sr-text:before {
42
+ animation-delay: .25s;
43
+ }
44
+
45
+ &:after {
46
+ animation-delay: .5s;
47
+ }
48
+
49
+ display: flex;
50
+ gap: var(--_ld-gap);
51
+ margin-top: var(--_ld-gap);
52
+ }
53
+
54
+ @media (prefers-reduced-motion:reduce){
55
+ .s-loader {
56
+ &:before,
57
+ & &--sr-text:before,
58
+ &:after {
59
+ animation: loader-animation-reduced-motion 2s ease-in-out infinite;
60
+ }
61
+ }
62
+ }
63
+
64
+ @keyframes loader-animation {
65
+ 0%,1%,99%,to{
66
+ opacity: 0.2;
67
+ transform: translateY(0);
68
+ }
69
+ 49%,50%{
70
+ opacity: 1;
71
+ transform: translateY(var(--_ld-offset));
72
+ }
73
+ 51%{
74
+ opacity: 0.2;
75
+ transform: translateY(var(--_ld-offset));
76
+ }
77
+ }
78
+
79
+ @keyframes loader-animation-reduced-motion {
80
+ 0%,to{
81
+ opacity: 0.3;
82
+ transform: none;
83
+ }
84
+ 50%{
85
+ opacity: 1;
86
+ transform: none;
87
+ }
88
+ }
@@ -27,8 +27,9 @@
27
27
  /**
28
28
  * Generate styles for a notice-based component
29
29
  *
30
- * Usage example:
30
+ * Usage examples:
31
31
  * .construct-notice-component(s-banner);
32
+ * .construct-notice-component(s-notice);
32
33
  *
33
34
  * @baseClass - The base class name for the notice component
34
35
  */
@@ -110,7 +111,6 @@
110
111
  &__warning {
111
112
  &:not(.@{baseClass}__important) {
112
113
  .generate-variant-variables(yellow);
113
-
114
114
  }
115
115
 
116
116
  &.@{baseClass}__important {
@@ -164,16 +164,26 @@
164
164
  background-color: var(--_no-fc);
165
165
  color: var(--_no-bg);
166
166
  }
167
+ color: var(--_no-fc);
167
168
  }
168
169
 
169
- :has(>button&--dismiss) {
170
+ &--actions {
171
+ display: flex;
172
+ margin-left: auto;
173
+ align-self: flex-start;
170
174
  padding-left: var(--su24);
171
- //Add spacing between multiple notice actions
175
+ color: var(--_no-fc);
172
176
  gap: calc(var(--su24) - var(--su2)); //22px
177
+
173
178
  //Fix css issue caused by svelte-sonner-toast in the NoticeAction svelte component
174
179
  overflow-wrap: initial !important;
175
180
  }
176
181
 
182
+ //Fix notice action color for __important variant
183
+ &--actions > .s-link:not(&--dismiss) {
184
+ color: var(--_no-fc);
185
+ }
186
+
177
187
  // STYLES MODIFIED BY COMPONENT-SPECIFIC CUSTOM PROPERTIES
178
188
  background: var(--_no-bg);
179
189
  color: var(--_no-fc);
@@ -195,10 +205,6 @@
195
205
  margin-right: var(--su12);
196
206
  align-self: stretch;
197
207
 
198
- //Position the svg icon
199
- display: flex;
200
- align-items: top;
201
-
202
208
  //Negative margin to make up for s-notice's padding
203
209
  margin-top: calc(var(--_no-pd) * -1);
204
210
  margin-bottom: calc(var(--_no-pd) * -1);
@@ -1,218 +1,48 @@
1
- .s-sidebarwidget {
2
- // COMPONENT-SPECIFIC CUSTOM PROPERTIES
3
- --_sw-bc: var(--bc-medium);
4
-
5
- // MODIFIERS
6
- &:not(.s-anchors) {
7
- a:not(.button):not(.s-tag):not(.post-tag):not(.s-btn):not(.s-sidebarwidget--action):not(.s-user-card--link) {
8
- &,
9
- &:visited {
10
- color: var(--black-600);
11
- }
12
- }
13
- }
14
-
15
- // VARIANTS
16
- .alternate-color(blue);
17
- .alternate-color(yellow);
18
- .alternate-color(green);
1
+ @headings: h1, h2, h3, h4, h5, h6;
19
2
 
3
+ .s-sidebarwidget {
20
4
  // CHILD ELEMENTS
21
- & &--action {
22
- color: var(--blue-400);
23
- font-size: var(--fs-body1);
24
- font-weight: normal;
25
- margin-left: auto;
26
- }
27
-
28
- & &--content {
29
- &:not(table) {
30
- &:not(.s-sidebarwidget__items),
31
- &:not(.s-sidebarwidget__block-items) .s-sidebarwidget--item {
32
- display: flex;
33
- }
34
- }
35
-
36
- + .s-sidebarwidget--content {
37
- border-top: var(--su-static1) solid var(--bc-light);
38
- }
39
-
40
- &.s-sidebarwidget__items {
41
- &,
42
- &.s-sidebarwidget__block-items .s-sidebarwidget--item {
43
- display: block;
44
- }
45
-
46
- padding: var(--su6) var(--su16); // the items themselves provide part of the spacing, so the content padding needs to account for that
5
+ & &--content {
6
+ .s-sidebarwidget--action {
7
+ font-size: var(--fs-fine);
8
+ margin-left: var(--su16);
9
+ align-self: flex-start;
47
10
  }
48
11
 
49
- &:active {
50
- outline: none;
51
- }
52
-
53
-
54
- margin: 0;
55
- padding: var(--su16);
12
+ display: flex;
13
+ padding: var(--su12) 0 var(--su16) 0;
14
+ font-size: var(--fs-body2);
56
15
  }
57
16
 
58
17
  & &--header {
59
- &:first-child {
60
- border-top: none;
61
- }
62
-
63
- + .s-expandable:not(.is-expanded) {
64
- margin-bottom: var(--su16);
65
- }
66
-
67
- &.s-sidebarwidget {
68
- &__expanding-control {
69
- &:before {
70
- border: calc(var(--su-static4) + var(--su-static1)) solid transparent;
71
- border-left-color: var(--black-400);
72
- border-right-width: 0;
73
- content: '';
74
- float: left;
75
- margin-right: var(--su12);
76
- margin-top: calc(calc(var(--lh-base) * 1em) / 2 - 5px); // 1.3 is our standard line height
77
- transition: transform 0.3s cubic-bezier(0.4, 0.4, 0.6, 1);
78
- }
79
-
80
- &[aria-expanded='true']:before {
81
- transform: rotate(90deg);
82
- }
83
-
84
- cursor: pointer;
85
- }
86
-
87
- &__small-bold-text {
88
- .s-sidebarwidget--action {
89
- font-weight: normal;
90
- line-height: calc(var(--lh-base) * var(--fs-caption)); // line-height should be the same as in the outside element, so the header and action baselines line up
91
- }
92
-
93
- font-size: var(--fs-caption);
94
- font-weight: bold;
95
- }
18
+ > @{headings} {
19
+ margin: 0;
20
+ padding-right: var(--su6);
21
+ font-size: var(--fs-body1);
22
+ font-weight: 500;
96
23
  }
97
24
 
98
- &:active {
99
- outline: none;
25
+ .s-sidebarwidget--action {
26
+ margin-left: auto;
100
27
  }
101
28
 
102
29
  align-items: center;
103
- border-top: var(--su-static1) solid var(--bc-light);
104
- color: var(--black-600);
105
30
  display: flex;
106
- font-size: var(--fs-body2);
107
- font-weight: bold;
108
- justify-content: flex-start;
109
- line-height: var(--lh-xs);
110
- margin: 0;
111
- padding: var(--su16) var(--su16) 0;
31
+ padding: var(--su4) 0;
112
32
  }
113
33
 
114
- & &--item {
115
- &,
116
- & > :first-child {
117
- &[aria-current="true"],
118
- &[aria-current="page"] {
119
- &:before {
120
- border-left-color: var(--theme-primary);
121
- border-left-style: solid;
122
- border-left-width: calc(var(--su-static1) * 3); // 3px
123
- content: '';
124
- height: calc(100% + var(--su16));
125
- left: 0;
126
- margin-left: var(--sun16); // the orange selection indicator overlaps with the widget border
127
- margin-top: var(--sun8);
128
- position: absolute;
129
- }
130
-
131
- a { // TODO: this isn't the best way to go about this. There should be a "is current" highlight without font modification for more complex cases
132
- &,
133
- &:visited {
134
- color: inherit;
135
- }
136
- }
137
-
138
- color: var(--black);
139
- font-weight: bold;
140
- position: relative;
141
- }
34
+ & &--footer {
35
+ .s-sidebarwidget--action {
36
+ flex: 1;
142
37
  }
143
-
144
- margin: var(--su12) 0;
145
- }
146
-
147
- & &--subnav {
148
- li {
149
- &[aria-current="page"],
150
- &[aria-current="true"] {
151
- a {
152
- &,
153
- &:visited {
154
- color: inherit;
155
- }
156
- }
157
-
158
- #stacks-internals #bullet-arrow(var(--theme-primary));
159
- color: var(--black);
160
- font-weight: bold;
161
- }
162
-
163
- #stacks-internals #bullet-arrow(var(--black-225));
164
- background-position: 0 calc((1.2em - calc(var(--su-static8) + var(--su-static2))) / 2); // 0 ((1.2em - 10) / 2)
165
- background-repeat: no-repeat;
166
- background-size: auto calc(var(--su-static8) + var(--su-static2)); // auto 10px
167
- margin-top: var(--su-static12);
168
- padding-left: var(--su-static16);
169
- }
170
-
171
- list-style-type: none;
172
- margin-left: var(--su8);
173
- padding-left: 0;
38
+
39
+ display: flex;
40
+ font-size: var(--fs-body2);
174
41
  }
175
42
 
176
- & table&--content&__items {
177
- tr.s-sidebarwidget--item {
178
- td {
179
- padding: 0;
180
- }
181
-
182
- display: table-row;
183
- }
184
-
185
- border-collapse: separate;
186
- border-spacing: var(--su16) var(--su12);
187
- padding: var(--su6) 0 0;
43
+ & &--action:is(a, button) {
44
+ white-space: nowrap;
188
45
  }
189
46
 
190
- background-color: var(--white);
191
- border: var(--su-static1) solid var(--_sw-bc);
192
- border-radius: var(--br-md);
193
47
  font-size: var(--fs-body1);
194
- }
195
-
196
- // COLOR ALTERNATIVES
197
- .alternate-color(@name) {
198
- &.s-sidebarwidget__@{name} {
199
- --_sw-bc: var(~"--@{name}-300");
200
-
201
- .highcontrast-mode({
202
- --_sw-bc: var(~"--@{name}-500");
203
- });
204
-
205
- &:after,
206
- .s-sidebarwidget--content + .s-sidebarwidget--content,
207
- .s-sidebarwidget--header {
208
- border-color: var(--_sw-bc);
209
- }
210
-
211
- .s-sidebarwidget--header {
212
- color: var(--fc-medium);
213
- }
214
-
215
- background-color: var(~"--@{name}-100");
216
- border-color: var(--_sw-bc);
217
- }
218
48
  }
@@ -17,6 +17,9 @@
17
17
  padding-bottom: var(--su8);
18
18
  padding-top: var(--su8);
19
19
  pointer-events: all;
20
+ display: flex;
21
+ align-items: center;
22
+ min-width: var(--su448);
20
23
  }
21
24
 
22
25
  display: flex;
@@ -42,7 +42,7 @@
42
42
  @import "components/prose/prose.less";
43
43
  @import "components/select/select.less";
44
44
  @import "components/sidebar-widget/sidebar-widget.less";
45
- @import "components/spinner/spinner.less";
45
+ @import "components/loader/loader.less";
46
46
  @import "components/table/table.less";
47
47
  @import "components/table-container/table-container.less";
48
48
  @import "components/tag/tag.less";
@@ -23,6 +23,7 @@ const scheduleVisualTest = ({
23
23
  }
24
24
 
25
25
  let retryAttempts = 3;
26
+ let lastError: Error | null = null;
26
27
 
27
28
  do {
28
29
  await fixture(element);
@@ -37,21 +38,22 @@ const scheduleVisualTest = ({
37
38
  return;
38
39
  } catch (error) {
39
40
  const e = error as Error;
40
- // if the error is not a visual diff failure, retry
41
- // this is to prevent flaky tests due to snapshot capturing
42
- if (
43
- retryAttempts > 0 &&
44
- !e.message.includes("Visual diff failed.")
45
- ) {
46
- retryAttempts--;
47
- continue;
48
- } else {
41
+ lastError = e;
42
+ // if the error is a visual diff failure, fail immediately
43
+ if (e.message.includes("Visual diff failed.")) {
49
44
  throw e;
50
45
  }
46
+ // otherwise retry (to prevent flaky tests due to snapshot capturing)
47
+ retryAttempts--;
51
48
  } finally {
52
49
  el.remove();
53
50
  }
54
51
  } while (retryAttempts > 0);
52
+
53
+ // If we exhausted all retries without success, throw the last error
54
+ if (lastError) {
55
+ throw lastError;
56
+ }
55
57
  });
56
58
  };
57
59
 
@@ -60,4 +62,34 @@ const runVisualTests = (args: VisualTestArgs) => {
60
62
  testVariations.forEach(scheduleVisualTest);
61
63
  };
62
64
 
63
- export { runVisualTests };
65
+ const replaceHtml = (
66
+ componentTemplateResult: unknown,
67
+ textToReplace: string,
68
+ replacementHtml: string
69
+ ) => {
70
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
71
+ const component = componentTemplateResult as any;
72
+ if (!Array.isArray(component.strings) || !Array.isArray(component.values)) {
73
+ throw new Error("Expected Lit TemplateResult type");
74
+ }
75
+
76
+ // Replace placeholder with actual icon in the Lit template
77
+ const originalStrings = component.strings;
78
+ const updatedStrings = originalStrings.map((str: string) =>
79
+ str.replace(textToReplace, replacementHtml)
80
+ );
81
+ // Create a proper TemplateStringsArray with raw property
82
+ Object.defineProperty(updatedStrings, "raw", {
83
+ value: updatedStrings.map((str: string) => str),
84
+ enumerable: false,
85
+ });
86
+ // Reconstruct the template with updated strings and original values
87
+ const updatedComponent = {
88
+ ...component,
89
+ strings: updatedStrings,
90
+ values: component.values,
91
+ };
92
+ return updatedComponent;
93
+ };
94
+
95
+ export { runVisualTests, replaceHtml };